第一节 MyBatis简介
1.1 MyBatis概述
- MyBatis 原本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,实质上Mybatis对ibatis进行了一些改进。
- MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
- 对jdbc的封装框架有:Hibernate、dbutils、jdbcTemplate[spring]、mybatis
- 原理:Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
1.2 以前的jdbc程序代码
package com.it.test;
import org.junit.Test;
import java.sql.*;
/**
* @ClassName Demo02
* @Author shuyy
* @Date 2020/9/18
**/
public class Demo02 {
@Test
public void test1() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1.加载数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.通过驱动管理类获取数据库链接
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/web-ssh", "root", "root");
//3.定义sql语句?表示占位符
String sql = "select * from t_user where username = ?";
//4.获取预处理statement
preparedStatement = connection.prepareStatement(sql);
//5.设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "shu");
//6.向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//7.遍历查询结果集
while (resultSet.next()) {
System.out.println("id:"+resultSet.getString("id") + " 用户名:" + resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//8.释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
数据库连接频繁开启和关闭,会严重影响数据库的性能。
代码中存在硬编码,分别是数据库部分的硬编码和SQL执行部分的硬编码。
1.3 MyBatis框架的核心
- mybatis配置文件,包括Mybatis全局配置文件和Mybatis映射文件,其中全局配置文件配置了数据源、事务等信息;映射文件配置了SQL执行相关的信息。
- mybatis通过读取配置文件信息(全局配置文件和映射文件),构造出SqlSessionFactory,即会话工厂。
- 通过SqlSessionFactory,可以创建SqlSession即会话。Mybatis是通过SqlSession来操作数据库的。
- SqlSession本身不能直接操作数据库,它是通过底层的Executor执行器接口来操作数据库的。Executor接口有两个实现类,一个是普通执行器,一个是缓存执行器(默认)。
- Executor执行器将要处理的SQL信息封装到一个底层对象MappedStatement中。该对象包括:SQL语句、输入参数映射信息、输出结果集映射信息。其中输入参数和输出结果的映射类型包括HashMap集合对象、POJO对象类型。
第二节 Mybatis入门
2.1 环境搭建
1. 下载Mybatis
-
mybaits由github.com管理,下载地址:https://github.com/mybatis/mybatis-3/releases.
-
这里使用MyBatis3.2.7
2. 创建数据库表
- 在Navicat中创建数据库mybatis_day01,选中该数据库,按F6进入命令行,复制以下sql,enter执行
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 80013
Source Host : localhost:3306
Source Database : mybatis_day01
Target Server Type : MYSQL
Target Server Version : 80013
File Encoding : 65001
Date: 2020-09-18 18:14:13
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for items
-- ----------------------------
DROP TABLE IF EXISTS `items`;
CREATE TABLE `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL COMMENT '商品名称',
`price` float(10,1) NOT NULL COMMENT '商品定价',
`detail` text COMMENT '商品描述',
`pic` varchar(64) DEFAULT NULL COMMENT '商品图片',
`createtime` datetime NOT NULL COMMENT '生产日期',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of items
-- ----------------------------
INSERT INTO `items` VALUES ('1', '台式机', '10000.0', '电脑质量非常好!', null, '2020-09-18 13:22:53');
INSERT INTO `items` VALUES ('2', '笔记本', '7000.0', '笔记本性能好,质量好!', null, '2020-09-18 13:22:57');
INSERT INTO `items` VALUES ('3', '背包', '500.0', '名牌背包,容量大质量好!', null, '2020-09-18 13:23:02');
-- ----------------------------
-- Table structure for orderdetail
-- ----------------------------
DROP TABLE IF EXISTS `orderdetail`;
CREATE TABLE `orderdetail` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`orders_id` int(11) NOT NULL COMMENT '订单id',
`items_id` int(11) NOT NULL COMMENT '商品id',
`items_num` int(11) DEFAULT NULL COMMENT '商品购买数量',
PRIMARY KEY (`id`),
KEY `FK_orderdetail_1` (`orders_id`),
KEY `FK_orderdetail_2` (`items_id`),
CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`),
CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of orderdetail
-- ----------------------------
INSERT INTO `orderdetail` VALUES ('1', '3', '1', '1');
INSERT INTO `orderdetail` VALUES ('2', '3', '2', '3');
INSERT INTO `orderdetail` VALUES ('3', '4', '3', '4');
INSERT INTO `orderdetail` VALUES ('4', '4', '2', '3');
-- ----------------------------
-- Table structure for orders
-- ----------------------------
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '下单用户id',
`number` varchar(32) NOT NULL COMMENT '订单号',
`createtime` datetime NOT NULL COMMENT '创建订单时间',
`note` varchar(100) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
KEY `FK_orders_1` (`user_id`),
CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of orders
-- ----------------------------
INSERT INTO `orders` VALUES ('3', '1', '1000010', '2020-09-18 13:22:35', null);
INSERT INTO `orders` VALUES ('4', '1', '1000011', '2020-09-18 13:22:41', null);
INSERT INTO `orders` VALUES ('5', '10', '1000012', '2020-09-18 13:22:50', null);
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`birthday` date DEFAULT NULL COMMENT '生日',
`sex` char(1) DEFAULT NULL COMMENT '性别',
`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '王五', '2020-09-18', '2', '南京市');
INSERT INTO `user` VALUES ('10', '张三', '2020-09-18', '1', '北京市');
INSERT INTO `user` VALUES ('27', '张大', '2020-09-18', '1', '上海市');
3. 创建项目导包
- 创建一个普通的java项目即可(演示一些mybatis的功能)
- 导入下载好的MyBatis的jar包
- 其中的pdf是mybatis自带的使用教程,从中可以获取一些文件配置。
- 导入数据库驱动包
- 发现mybatis里有很多log4j的包
4. 添加log4j.properties
- Mybatis使用的日志包是log4j的,所以添加log4j.properties。
在classpath下创建log4j.properties内容如下:【文件内容可以从mybatis-3.2.7.pdf中拷贝】
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
- 日志级别在开发阶段设置成DEBUG,在生产阶段设置成INFO或者ERROR。
- 这里暂不配置写入文件中
2.2 开发步骤
1. 根据需求创建PO(model)类
- User.java
package com.it.model;
import java.io.Serializable;
import java.util.Date;
/**
* @ClassName User
* @Author shuyy
* @Date 2020/9/19
**/
public class User implements Serializable {
private int id;
private String username;//用户姓名
private String sex;//性别,1女2男
private Date birthday;//生日
private String address;//地址
public User() {
}
//id自增无需添加
public User(String username, String sex, Date birthday, String address) {
this.username = username;
this.sex = sex;
this.birthday = birthday;
this.address = address;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", sex=" + sex
+ ", birthday=" + birthday + ", address=" + address + "]";
}
}
2. 创建全局配置文件SqlMapConfig.xml
- 名字可以自定义,这里使用SqlMapConfig.xml
- 在classpath(src)下,创建SqlMapConfig.xml文件【SqlMapConfig.xml(文件头可以从mybatis-3.2.7.pdf文档的2.1.2小节中拷贝)】
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置mybatis的环境信息(可配置多个环境,配置多个数据源)-->
<environments default="development">
<environment id="development">
<!-- 配置JDBC事务控制,由mybatis进行管理,如果不配置会报错 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源,采用dbcp连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis_day01"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
3. 编写映射文件
- 在classpath下,创建sqlmap文件夹。在sqlmap目录下,创建User.xml映射文件。(与hibernate的使用类似)
【Mybatis的映射文件头(也可以从mybatis-3.2.7.pdf文件中拷贝)】
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
namespace:命名空间,它的作用就是对SQL进行分类化管理,可以理解为SQL隔离
注意:使用mapper代理开发时,namespace有特殊且重要的作用
-->
<mapper namespace="test">
<!--
id:statement的id,要求在命名空间内唯一
parameterType:参数的java类型
resultType:查询出的单条结果集对应的java类型
#{}:表示一个占位符 ?
#{id}:表示该占位符待接收参数的名称为id。注意:如果参数为简单类型时,#{}里面的参数名称可以是任意定义
-->
<select id="findUserById" parameterType="int" resultType="com.it.model.User">
SELECT * FROM USER WHERE id = #{id}
</select>
</mapper>
4. 加载映射文件(在SqlMapConfig.xml中配置)
<!--加载映射文件-->
<mappers>
<mapper resource="com/it/sqlmap/User.xml"></mapper>
</mappers>
5. 编写测试程序,连接并操作数据库
package com.it.test;
import com.it.model.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
/**
* @ClassName Demo01
* @Author shuyy
* @Date 2020/9/19
**/
public class Demo01 {
@Test
public void test1() throws IOException {
//1.读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂
SqlSessionFactoryBuilder sessionFactory = new SqlSessionFactoryBuilder();
//3.通过SqlSessionFactory创建SqlSession
SqlSession sqlSession = sessionFactory.build(inputStream).openSession();
//4.调用SqlSession的操作数据库方法
//selectOne查询一条记录
User user = sqlSession.selectOne("findUserById",1);
System.out.println(user);//查询结果返回的就是一个User对象
//5.关闭SqlSession
sqlSession.close();
}
}
2.3 更多操作
1. 模糊查询
<!--
${}:表示拼接SQL字符串
${value}:表示要拼接的是简单类型参数。
注意:
简单类型:int、byte、...String
1.如果参数为简单类型时,${}里面的参数名称必须为value
2.${}会引起SQL注入,一般情况下不推荐使用。但是有些场景必须使用${},比如(排序)order by ${name}
-->
<select id="findUserByName" parameterType="String" resultType="com.it.model.User">
SELECT * FROM USER WHERE username like '%${value}%'
</select>
- selectList查询多条记录
2. 抽取重复代码(简化书写)
- 发现每次都要写前面的固定代码,下面来抽取一下,简化书写
package com.it.test;
import com.it.model.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* @ClassName Demo02
* @Author shuyy
* @Date 2020/9/19
**/
public class Demo02 {
SqlSession sqlSession;
@Before
public void before() throws IOException {
System.out.println("before...获取session");
//1.读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂
SqlSessionFactoryBuilder sessionFactory = new SqlSessionFactoryBuilder();
//3.通过SqlSessionFactory创建SqlSession
sqlSession = sessionFactory.build(inputStream).openSession();
}
@After
public void after(){
System.out.println("after...关闭session");
//5.关闭SqlSession
sqlSession.close();
}
@Test
public void test1(){
//4.调用SqlSession的操作数据库方法
//selectList查询多条记录(如果符合结果的记录有多条还使用selectOne会报错哦)
List<User> list = sqlSession.selectList("findUserByName","张");
System.out.println(list);//查询结果返回的是一个list集合
}
}
- 简化了很多
3. 插入
<!--插入用户信息
这里的占位写的是模型的属性,对应一下即可
-->
<insert id="insertUser" parameterType="com.it.model.User">
insert into user (username,sex,birthday,address)
values (#{username},#{sex},#{birthday},#{address})
</insert>
- 运行成功了,但是数据没有插入,是因为涉及增删改要提交事务(查询不用)
@Test
public void test2(){
//插入
//4.调用SqlSession的操作数据库方法
User user = new User("shu", "男", new Date(), "北京市");
sqlSession.insert("insertUser",user);
sqlSession.commit();//增删改要提交事务,否则不影响数据库
}
- sqlSession调用返回的是一个int类型的影响行数
4. 删除
<!--删除用户-->
<delete id="deleteUserById" parameterType="int">
delete from user where id = #{id}
</delete>
@Test
public void test3(){
//删除用户
//4.调用SqlSession的操作数据库方法
int affectRow = sqlSession.delete("deleteUserById", 32);//返回的是一个int类型,受影响的行数
System.out.println("受影响的行数:"+affectRow);
sqlSession.commit();//增删改要提交事务,否则不影响数据库
}
5. 更新
<!--更新用户-->
<update id="updateUser" parameterType="com.it.model.User">
update user set username = #{username},birthday = #{birthday},sex = #{sex},address = #{address}
where id = #{id}
</update>
@Test
public void test4(){
//更新
User user = new User();
user.setId(29);
user.setUsername("shu03");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("上海市");
//4.调用SqlSession的操作数据库方法
int affectRow = sqlSession.update("updateUser", user);//返回的是一个int类型,受影响的行数
System.out.println("受影响的行数:"+affectRow);
sqlSession.commit();//增删改要提交事务,否则不影响数据库
}
6. 插入后自动返回主键(id)
- MySQL自增主键,是指在insert之前MySQL会自动生成一个自增的主键。
- 我们可以通过MySQL的函数获取到刚插入的自增主键:LAST_INSERT_ID()
- 这个函数是在insert语句之后去调用。
- 这里如果使用BEFORE(before)会报错
<!--插入后自动返回主键id-->
<insert id="insertUser2" parameterType="com.it.model.User">
<!--
selectKey标签:通过select查询来生成主键
keyProperty:指定存放生成主键的属性
resultType:生成主键所对应的Java类型
order:指定该查询主键SQL语句的执行顺序,相对于insert语句
last_insert_id:MySQL的函数,要配合insert语句一起使用
-->
<selectKey keyProperty="id" resultType="int" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
insert into user (username,sex,birthday,address)
values (#{username},#{sex},#{birthday},#{address})
</insert>
@Test
public void test5(){
//插入后返回主键id
User user = new User("shu04","男",new Date(),"南京市");
//4.调用SqlSession的操作数据库方法
int affectRow = sqlSession.insert("insertUser2", user);//返回的是一个int类型,受影响的行数
System.out.println("受影响的行数:"+affectRow);
sqlSession.commit();//增删改要提交事务,否则不影响数据库
System.out.println("用户的id:"+user.getId());
}
7. 插入后自动返回主键(uuid)
- 首先要生成一张主键id为uuid类型的表
- 然后修改id的类型为string,重新提供一下get/set
- 上面的id是mysql自增机制生成的,无需给id赋值,这里的uuid不同,需要给它赋值,否则报错
- 这里使用的是BEFORE(before使用after会报错)
<!--插入后自动返回主键uuid-->
<insert id="insertUser3" parameterType="com.it.model.User">
<!--
selectKey标签:通过select查询来生成主键
keyProperty:指定存放生成主键的属性
resultType:生成主键所对应的Java类型
order:指定该查询主键SQL语句的执行顺序,相对于insert语句
-->
<selectKey keyProperty="id" resultType="String" order="BEFORE">
select uuid()
</selectKey>
insert into user (id,username)
values (#{id},#{username})
</insert>
@Test
public void test6(){
//插入后返回主键uuid
User user = new User();
user.setUsername("shu06");
//4.调用SqlSession的操作数据库方法
int affectRow = sqlSession.insert("insertUser3", user);//返回的是一个int类型,受影响的行数
System.out.println("受影响的行数:"+affectRow);
sqlSession.commit();//增删改要提交事务,否则不影响数据库
System.out.println("用户的id:"+user.getId());
}
- 注意它插入的位置,是在上一条记录前(shu05是先插入的)
小结
parameterType和resultType
- parameterType指定输入参数的java类型,可以写别名或Java类的全限定名。
- resultType指定输出结果的java类型,可以写别名或Java类的全限定名。
#{}和${}
- #{}:相当于预处理中的占位符?。
- #{}里面的参数表示接收java输入参数的名称。
- #{}可以接受HashMap、POJO类型的参数。
- 当接受简单类型的参数时,#{}里面可以是value,也可以是其它。
- #{}可以防止SQL注入
- ${}:相当于拼接SQL串,对传入的值不做任何解释的原样输出。
- ${}会引起SQL注入,所以要谨慎使用。
- ${}可以接受HashMap、POJO类型的参数。
- 当接受简单类型的参数时,${}里面只能是value。
selectOne和selectList
- selectOne:只能查询0或1条记录,大于1条记录的话,会报错
- selectList:可以查询0或N条记录