Mybatis 入门教程

Mybatis 入门教程


mybatis 的入门

特别提示: mybatis 官方教程:https://mybatis.org/mybatis-3/zh/index.html)

既然要学习框架,那就要知道 什么是框架框架 是我们软件开发中的 一套解决方案 ,相当于一个半成品软件,不同的框架解决的是不同的问题。使用框架的好处 :框架封装了很多的细节,使开发者可以使用极简的方式实现功能,大大提高开发效率

mybatis 的概述

mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者 只需要关注 sql 语句本身 ,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。


它通过 xml 或 注解 的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回


该框架采用 ORM 思想 解决了 实体和数据库映射的问题 ,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。这里解释一下 ORMORMObject Relational Mappging 对象关系映射 的缩写,它的功能就是 把数据库表和实体类及实体类的属性对应起来 ,让我们可以操作实体类就实现操作数据库表。


mybatis 属于持久层框架 ,在三层架构中的位置如下:

mybatis 的环境搭建

第一步: 先创建一个数据库 ,然后把以下 数据表 创建好,复制代码执行即可 ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL auto_increment,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`birthday` datetime default NULL COMMENT '生日',
`sex` char(1) default NULL COMMENT '性别',
`address` varchar(256) default NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'),(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),(46,'老王','2018-03-07 17:37:26','男','北京'),(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');



DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`ID` int(11) NOT NULL COMMENT '编号',
`UID` int(11) default NULL COMMENT '用户编号',
`MONEY` double default NULL COMMENT '金额',
PRIMARY KEY (`ID`),
KEY `FK_Reference_8` (`UID`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `account`(`ID`,`UID`,`MONEY`) values (1,41,1000),(2,45,1000),(3,41,2000);



DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`ID` int(11) NOT NULL COMMENT '编号',
`ROLE_NAME` varchar(30) default NULL COMMENT '角色名称',
`ROLE_DESC` varchar(60) default NULL COMMENT '角色描述',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) values (1,'院长','管理整个学院'),(2,'总裁','管理整个公司'),(3,'校长','管理整个学校');



DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`UID` int(11) NOT NULL COMMENT '用户编号',
`RID` int(11) NOT NULL COMMENT '角色编号',
PRIMARY KEY (`UID`,`RID`),
KEY `FK_Reference_10` (`RID`),
CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`),
CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `user_role`(`UID`,`RID`) values (41,1),(45,1),(41,2);

第二步: 创建一个 maven 工程 ,以图的方式展示创建过程,如下:




第三步: 导入相关依赖 。打开 pom.xml 文件,把以下 代码中的 <dependencies> 依赖部分 复制进去即可【打包方式顺便加上】。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>club.guoshizhan</groupId>
<artifactId>mybatis-01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>

<!-- mybatis 依赖,必须要 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>

<!-- mysql 数据库驱动依赖,必须要 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>

<!-- 日志依赖,不是一定要 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>

<!-- 测试依赖,不是一定要 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>

</dependencies>
</project>

第四步: 建立包结构 。如下图:

第五步:club/guoshizhan/domain 包下新建 User 类 ,和数据库中的 user 表 相映射。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package club.guoshizhan.domain;

import java.io.Serializable;
import java.util.Date;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:52
* @Description: User 实体类
*/
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}
}

第六步:club/guoshizhan/dao 包下新建 IUserDao 接口 ,然后定义方法,用于对 user 表 的增删改查。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
List<User> findAll();

}

第七步:resources 目录 下新建 SqlMapConfig.xml 文件【文件名随意取,不一定是 SqlMapConfig】。这是 mybatis 的主配置文件,各种配置都写好了对应的注释 。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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">

<!-- 01-mybatis 的主配置文件 -->
<configuration>
<!-- 02-配置 mybatis 的环境,default 属性的值随便写,写完之后,下面 id 的值必须和 default 的值一样 -->
<environments default="mysql">

<!-- 03-配置 mysql 的环境 -->
<environment id="mysql">

<!-- 04-配置事务的类型,type 先写 JDBC ,其他的以后说 -->
<transactionManager type="JDBC"></transactionManager>

<!-- 05-配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>

</environment>

</environments>

<!-- 06-指定 mybatis 映射配置的位置,即从 resources/club/guoshizhan/dao/ 包下找 IUserDao.xml 文件 -->
<mappers>
<mapper resource="club/guoshizhan/dao/IUserDao.xml"/>
</mappers>

</configuration>

第八步: 编写 IUserDao.xml 文件 。在 resources 目录 下新建 club 目录 ,然后在 club 目录 下新建 guoshizhan 目录 ,最后在 guoshizhan 目录 下新建 dao 目录然后在 dao 目录 下新建 IUserDao.xml 文件。*** 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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">

<!-- 01-持久层映射配置中,mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名 -->
<mapper namespace="club.guoshizhan.dao.IUserDao">

<!-- 02-查询所有用户:resultType 很重要,是查询结果的封装位置,不写会报错,表示 mybatis 框架不知道把结果往哪里封装 -->
<!-- 03-属性 id 不能随便写,必须是接口中方法的名称 -->
<select id="findAll" resultType="club.guoshizhan.domain.User">
select * from user;
</select>

</mapper>

mybatis 环境搭建到此结束,搭建好之后的目录结构 如下:

mybatis 环境搭建的注意事项:
1、在 Mybatis 中,持久层的操作接口名称和映射文件也叫做 Mapper 。所以 IUserDao 和 IUserMapper 是一样的。建议使用 IUserMapper 。 2、在 IDEA 中创建目录的时候,它和包的创建是不一样的。包在创建时: club.guoshizhan.dao 是三级结构,目录在创建时: club.guoshizhan.dao 是一级目录。 3、mybatis 的映射配置文件 IUserDao.xml 的位置必须和 dao 接口的包结构相同。 4、映射配置文件的 mapper 标签 namespace 属性的取值必须是 dao 接口的全限定类名。 5、映射配置文件的操作配置(select),id 属性的取值必须是 dao 接口的方法名。 6、当遵从了第三,四,五点之后,我们在开发中就无须再写 dao 的实现类,即简化了开发。

mybatis 的入门案例

第一步:resources 目录下 新建 log4j.properties 日志文件 ,用于打印日志【也可以不写,不写就看不到出错的地方,建议加上】。代码如下:

log4j.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE

# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

第二步:test/java 目录下 新建 club.guoshizhan.Test.MybatisTest 类,这样写的目的就是让 IDEA 自动生成三级包结构。然后编写 MybatisTest 类 ,代码如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {
public static void main(String[] args) throws Exception {

// 01-读取 mybatis 主配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

// 02-创建 SqlSessionFactory 工厂 ,SqlSessionFactory 是一个接口,不能 new 对象,所以使用它的实现类 SqlSessionFactoryBuilder 来创建对象
SqlSessionFactoryBuilder factory = new SqlSessionFactoryBuilder();
SqlSessionFactory build = factory.build(in);

// 03-使用上面创建的工厂来生产 SqlSession 对象,SqlSession 的作用是创建 Dao 接口的代理对象
SqlSession session = build.openSession();

// 04-使用 SqlSession 创建 Dao 接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);

// 05-使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}

// 06-关闭资源
session.close();
in.close();

}
}

第三步: 运行 main 方法,结果如下:

没有日志信息
有日志信息

mybatis 入门案例到此结束,最终的目录结构和入门案例分析 如下:

最终目录结构
入门案例分析

mybatis 注解案例

第一步: 创建一个 maven 工程 ,以图的方式展示创建过程,如下:




第二步: 导入相关依赖 。打开 pom.xml 文件,把以下 代码中的 <dependencies> 依赖部分 复制进去即可【打包方式顺便加上】。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>club.guoshizhan</groupId>
<artifactId>mybatis-02</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 打包方式使用 jar -->
<packaging>jar</packaging>

<dependencies>

<!-- mybatis 依赖,必须要 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>

<!-- mysql 数据库驱动依赖,必须要 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>

<!-- 日志依赖,不是一定要 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>

<!-- 测试依赖,不是一定要 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>

</dependencies>

</project>

第三步: 建立包结构 。如下图:

第四步:club/guoshizhan/domain 包下新建 User 类 ,和数据库中的 user 表 相映射。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package club.guoshizhan.domain;

import java.io.Serializable;
import java.util.Date;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:52
* @Description: User 实体类
*/
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}
}

第五步:club/guoshizhan/dao 包下新建 IUserDao 接口 ,然后定义方法,用于对 user 表 的增删改查 【此处代码可是用到了注解哦】 。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户,此处使用的是注解的方式
@Select("select * from user")
List<User> findAll();

}

第六步:resources 目录 下新建 SqlMapConfig.xml 主配置文件,各种配置都写好了对应的注释【注意对比注释 06 和原先 xml 配置的区别】 。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?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">

<!-- 01-mybatis 的主配置文件 -->
<configuration>
<!-- 02-配置 mybatis 的环境,default 属性的值随便写,写完之后,下面 id 的值必须和 default 的值一样 -->
<environments default="mysql">

<!-- 03-配置 mysql 的环境 -->
<environment id="mysql">

<!-- 04-配置事务的类型,type 先写 JDBC ,其他的以后说 -->
<transactionManager type="JDBC"></transactionManager>

<!-- 05-配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>

</environment>

</environments>

<!-- 06-指定 mybatis 映射配置的位置,即从 resources/club/guoshizhan/dao/ 包下找 IUserDao.xml 文件
<mappers>
<mapper resource="club/guoshizhan/dao/IUserDao.xml"/>
</mappers>
-->

<!-- 06-如果使用注解的方式,那么此处应该使用 class 属性指定被注解的 Dao 的全限定类名 -->
<mappers>
<mapper class="club.guoshizhan.dao.IUserDao"/>
</mappers>

</configuration>

第七步:resources 目录下 新建 log4j.properties 日志文件 ,用于打印日志。代码如下:

log4j.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE

# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

第八步:test/java 目录下 新建 club.guoshizhan.Test.MybatisTest 类,这样写的目的就是让 IDEA 自动生成三级包结构。然后编写 MybatisTest 类 ,代码如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {
public static void main(String[] args) throws Exception {

// 01-读取 mybatis 主配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

// 02-创建 SqlSessionFactory 工厂 ,SqlSessionFactory 是一个接口,不能 new 对象,所以使用它的实现类 SqlSessionFactoryBuilder 来创建对象
SqlSessionFactoryBuilder factory = new SqlSessionFactoryBuilder();
SqlSessionFactory build = factory.build(in);

// 03-使用上面创建的工厂来生产 SqlSession 对象,SqlSession 的作用是创建 Dao 接口的代理对象
SqlSession session = build.openSession();

// 04-使用 SqlSession 创建 Dao 接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);

// 05-使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}

// 06-关闭资源
session.close();
in.close();

}
}

第九步: 运行 MybatisTest 测试类中的 main 方法,结果如下:

注解使用总结:

1、把 IUserDao.xml 移除,在 dao 接口中的方法上使用 @Select 注解,并且指定 SQL 语句。
2、同时需要在 SqlMapConfig.xml 中的 mapper 配置时,使用 class 属性指定 dao 接口的全限定类名。

自定义 mybatis 框架


第一步: 创建一个 Maven 工程。这里就不新创建了。具体创建可参考上一小节: mybatis 注解案例


第二步: 导入相关依赖 。打开 pom.xml 文件,复制如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>club.guoshizhan</groupId>
<artifactId>mybatis-03</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>

<!-- mybatis 依赖,这个依赖注释掉或删掉,因为我们要自己定义一个
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
-->

<!-- mysql 数据库驱动依赖,必须要 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>

<!-- 日志依赖,不是一定要 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>

<!-- 测试依赖,不是一定要 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>

<!-- 用于解析 XML ,必须要 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>

<!-- 与 xpath 相关 -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>

</dependencies>

</project>

第三步: 删除工程中的 src 目录 然后把以下压缩文件里的 src 目录 复制到工程中。做这一步的原因主要是不想再去重复创建上面几个小结的步骤,也是为了节省时间。代码都是看的懂的。压缩包点击即可下载: src 目录文件的压缩包 复制完成之后的工程截图如下:

第四步:guoshizhan 包 下新建 mybatis 包 ,然后在 mybatis 包中新建 Resources 类 。该类的代码如下:

Resources.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package club.guoshizhan.mybatis;

import java.io.InputStream;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 0:38
* @Description: 使用类加载器读取配置文件的类
*/
public class Resources {

// 根据传入的参数,获取一个字节输入流
public static InputStream getResourceAsStream(String filePath) {
return Resources.class.getClassLoader().getResourceAsStream(filePath);
}

}

接着在 mybatis 包 下新建 SqlSession 接口 ,该接口的代码如下:

SqlSession.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package club.guoshizhan.mybatis;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 0:52
* @Description: 自定义 SqlSession 接口,这是和数据库交互的核心接口,它里面可以创建 dao 接口的代理对象
*/
public interface SqlSession {

// 根据 dao 接口的字节码创建一个代理对象
<T> T getMapper(Class<T> daoInterfaceClass);

// 释放资源
void close();

}

然后在 mybatis 包 下新建 SqlSessionFactory 接口 ,该接口的代码如下:

SqlSessionFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
package club.guoshizhan.mybatis;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 0:47
* @Description: 自定义 SqlSessionFactory 接口
*/
public interface SqlSessionFactory {

// 用于打开一个新的 SqpSession 对象
SqlSession openSession();

}

最后在 mybatis 包 下新建 SqlSessionFactoryBuilder 类 ,该类初始代码如下【后面还需完善】:

SqlSessionFactoryBuilder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package club.guoshizhan.mybatis;

import java.io.InputStream;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 0:45
* @Description: 自定义 SqlSessionFactoryBuilder 类
*/
public class SqlSessionFactoryBuilder {

public SqlSessionFactory build(InputStream config) {
return null;
}

}

当类和接口创建好后,就去把 MybatisTest 测试类 中的报红问题解决。先删除无用的包,然后导入自定义的接口和类即可。最终 MybatisTest 测试类 代码和截图如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import club.guoshizhan.mybatis.Resources;
import club.guoshizhan.mybatis.SqlSession;
import club.guoshizhan.mybatis.SqlSessionFactory;
import club.guoshizhan.mybatis.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {
public static void main(String[] args) throws Exception {

// 01-读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

// 02-创建 SqlSessionFactory 工厂 ,SqlSessionFactory 是一个接口,不能 new 对象
SqlSessionFactoryBuilder factory = new SqlSessionFactoryBuilder();
SqlSessionFactory build = factory.build(in);

// 03-使用工厂生产 SqlSession 对象
SqlSession session = build.openSession();

// 04-使用 SqlSession 创建 Dao 接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);

// 05-使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}

// 06-关闭资源
session.close();
in.close();

}
}

第五步: 解析 XML 配置文件 。首先在 mybatis 包 下新建 Configuration 配置类 ,该类的代码如下:

Configuration.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package club.guoshizhan.mybatis;

import java.util.HashMap;
import java.util.Map;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 10:08
* @Description: 自定义 mybatis 的配置类
*/
public class Configuration {

private String driver;
private String url;
private String username;
private String password;
private Map<String, Mapper> mappers = new HashMap<>();

public Map<String, Mapper> getMappers() {
return mappers;
}

public void setMappers(Map<String, Mapper> mappers) {
this.mappers.putAll(mappers); // 采用追加方式,而不是覆盖【this.mappers = mappers】
}

public String getDriver() {
return driver;
}

public void setDriver(String driver) {
this.driver = driver;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

}

然后在 mybatis 包 下新建 Mapper 类 ,该类的代码如下:

Mapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package club.guoshizhan.mybatis;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 10:12
* @Description: 用于封装执行的 SQL 语句和结果类型的全限定类名
*/
public class Mapper {

private String queryString; // SQL
private String resultType; // 实体类的全限定类名

public String getQueryString() {
return queryString;
}

public void setQueryString(String queryString) {
this.queryString = queryString;
}

public String getResultType() {
return resultType;
}

public void setResultType(String resultType) {
this.resultType = resultType;
}

}

最后在 mybatis 包 下新建 XMLConfigBuilder 类 用于解析 xml 文件 。该类的代码如下:

XMLConfigBuilder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package club.guoshizhan.mybatis;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class XMLConfigBuilder {


/**
* 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方
* 使用的技术:dom4j + xpath
*/
public static Configuration loadConfiguration(InputStream config) {
try {
// 定义封装连接信息的配置对象(mybatis的配置对象)
Configuration cfg = new Configuration();

// 1.获取 SAXReader 对象
SAXReader reader = new SAXReader();
// 2.根据字节输入流获取 Document 对象
Document document = reader.read(config);
// 3.获取根节点
Element root = document.getRootElement();
// 4.使用 xpath 中选择指定节点的方式,获取所有 property 节点
List<Element> propertyElements = root.selectNodes("//property");
// 5.遍历节点
for (Element propertyElement : propertyElements) {
//判断节点是连接数据库的哪部分信息
//取出name属性的值
String name = propertyElement.attributeValue("name");
if ("driver".equals(name)) {
//表示驱动
//获取property标签value属性的值
String driver = propertyElement.attributeValue("value");
cfg.setDriver(driver);
}
if ("url".equals(name)) {
//表示连接字符串
//获取property标签value属性的值
String url = propertyElement.attributeValue("value");
cfg.setUrl(url);
}
if ("username".equals(name)) {
//表示用户名
//获取property标签value属性的值
String username = propertyElement.attributeValue("value");
cfg.setUsername(username);
}
if ("password".equals(name)) {
// 表示密码
// 获取 property 标签 value 属性的值
String password = propertyElement.attributeValue("value");
cfg.setPassword(password);
}
}
// 取出 mappers 中的所有 mapper 标签,判断他们使用了 resource 还是 class 属性
List<Element> mapperElements = root.selectNodes("//mappers/mapper");
// 遍历集合
for (Element mapperElement : mapperElements) {
// 判断 mapperElement 使用的是哪个属性
Attribute attribute = mapperElement.attribute("resource");
if (attribute != null) {
System.out.println("使用的是 XML ");
// 表示有 resource 属性,用的是XML
// 取出属性的值
String mapperPath = attribute.getValue(); // 获取属性的值"club/guoshizhan/dao/IUserDao.xml"
// 把映射配置文件的内容获取出来,封装成一个 map
Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
// 给 configuration 中的 mappers 赋值
cfg.setMappers(mappers);
} else {
/*System.out.println("使用的是注解");
//表示没有resource属性,用的是注解
//获取class属性的值
String daoClassPath = mapperElement.attributeValue("class");
//根据daoClassPath获取封装的必要信息
Map<String, Mapper> mappers = loadMapperAnnotation(daoClassPath);
//给configuration中的mappers赋值
cfg.setMappers(mappers);*/
}
}
// 返回 Configuration
return cfg;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
config.close();
} catch (Exception e) {
e.printStackTrace();
}
}

}

/**
* 根据传入的参数,解析XML,并且封装到Map中
*
* @param mapperPath 映射配置文件的位置
* @return map中包含了获取的唯一标识(key是由dao的全限定类名和方法名组成)
* 以及执行所需的必要信息(value是一个Mapper对象,里面存放的是执行的SQL语句和要封装的实体类全限定类名)
*/
private static Map<String, Mapper> loadMapperConfiguration(String mapperPath) throws IOException {
InputStream in = null;
try {
// 定义返回值对象
Map<String, Mapper> mappers = new HashMap<String, Mapper>();
// 1.根据路径获取字节输入流
in = Resources.getResourceAsStream(mapperPath);
// 2.根据字节输入流获取 Document 对象
SAXReader reader = new SAXReader();
Document document = reader.read(in);
// 3.获取根节点
Element root = document.getRootElement();
// 4.获取根节点的 namespace 属性取值
String namespace = root.attributeValue("namespace"); // 是组成 map 中 key 的部分
// 5.获取所有的 select 节点
List<Element> selectElements = root.selectNodes("//select");
// 6.遍历 select 节点集合
for (Element selectElement : selectElements) {
// 取出 id 属性的值【map 中 key 的部分】
String id = selectElement.attributeValue("id");
// 取出 resultType 属性的值 组成map中value的部分
String resultType = selectElement.attributeValue("resultType");
// 取出文本内容【map 中 value 的部分】
String queryString = selectElement.getText();
// 创建 Key
String key = namespace + "." + id;
// 创建 Value
Mapper mapper = new Mapper();
mapper.setQueryString(queryString);
mapper.setResultType(resultType);
// 把 key 和 value 存入 mappers 中
mappers.put(key, mapper);
}
return mappers;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
in.close();
}
}

/**
* 根据传入的参数,得到 dao 中所有被 select 注解标注的方法。
* 根据方法名称和类名,以及方法上注解 value 属性的值,组成 Mapper 的必要信息
*/
/*private static Map<String, Mapper> loadMapperAnnotation(String daoClassPath) throws Exception {
//定义返回值对象
Map<String, Mapper> mappers = new HashMap<String, Mapper>();

//1.得到dao接口的字节码对象
Class daoClass = Class.forName(daoClassPath);
//2.得到dao接口中的方法数组
Method[] methods = daoClass.getMethods();
//3.遍历Method数组
for (Method method : methods) {
//取出每一个方法,判断是否有select注解
boolean isAnnotated = method.isAnnotationPresent(Select.class);
if (isAnnotated) {
//创建Mapper对象
Mapper mapper = new Mapper();
//取出注解的value属性值
Select selectAnno = method.getAnnotation(Select.class);
String queryString = selectAnno.value();
mapper.setQueryString(queryString);
//获取当前方法的返回值,还要求必须带有泛型信息
Type type = method.getGenericReturnType();//List<User>
//判断type是不是参数化的类型
if (type instanceof ParameterizedType) {
//强转
ParameterizedType ptype = (ParameterizedType) type;
//得到参数化类型中的实际类型参数
Type[] types = ptype.getActualTypeArguments();
//取出第一个
Class domainClass = (Class) types[0];
//获取domainClass的类名
String resultType = domainClass.getName();
//给Mapper赋值
mapper.setResultType(resultType);
}
//组装key的信息
//获取方法的名称
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String key = className + "." + methodName;
//给map赋值
mappers.put(key, mapper);
}
}
return mappers;
}*/

}

第六步: 编写工具类和代理类 。首先在 mybatis 包 下新建 DataSourceUtil 类 ,该类的代码如下:

DataSourceUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package club.guoshizhan.mybatis;

import java.sql.Connection;
import java.sql.DriverManager;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 13:38
* @Description: 创建数据源的工具类 DataSourceUtil
*/
public class DataSourceUtil {

// 用于获取一个连接
public static Connection getConnection(Configuration cfg) {
try {
Class.forName(cfg.getDriver());
return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
} catch (Exception e) {
throw new RuntimeException(e);
}
}

}

接着在 mybatis 包 下新建 Executor 类 ,该类的代码如下:

Executor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package club.guoshizhan.mybatis;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

public class Executor {

public <E> List<E> selectList(Mapper mapper, Connection conn) {
PreparedStatement pstm = null;
ResultSet rs = null;
try {
// 1.取出 mapper 中的数据
String queryString = mapper.getQueryString(); // select * from user
String resultType = mapper.getResultType();
Class domainClass = Class.forName(resultType);
// 2.获取 PreparedStatement 对象
pstm = conn.prepareStatement(queryString);
// 3.执行 SQL 语句,获取结果集
rs = pstm.executeQuery();
// 4.封装结果集
List<E> list = new ArrayList<>(); // 定义返回值
while (rs.next()) {
// 实例化要封装的实体类对象
E obj = (E) domainClass.newInstance();

// 取出结果集的元信息:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 取出总列数
int columnCount = rsmd.getColumnCount();
// 遍历总列数
for (int i = 1; i <= columnCount; i++) {
// 获取每列的名称,列名的序号是从1开始的
String columnName = rsmd.getColumnName(i);
// 根据得到列名,获取每列的值
Object columnValue = rs.getObject(columnName);
// 给obj赋值:使用 Java 内省机制(借助 PropertyDescriptor 实现属性的封装)
PropertyDescriptor pd = new PropertyDescriptor(columnName, domainClass); // 要求:实体类的属性和数据库表的列名保持一种
// 获取它的写入方法
Method writeMethod = pd.getWriteMethod();
// 把获取的列的值,给对象赋值
writeMethod.invoke(obj, columnValue);
}
// 把赋好值的对象加入到集合中
list.add(obj);
}
return list;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
release(pstm, rs);
}
}


private void release(PreparedStatement pstm, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
}

if (pstm != null) {
try {
pstm.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

最后在 mybatis 包 下新建 MapperProxy 类 ,该类的代码如下:

MapperProxy.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package club.guoshizhan.mybatis;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Map;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 13:21
* @Description: MapperProxy 代理类
*/
public class MapperProxy implements InvocationHandler {

private Map<String, Mapper> mappers;
private Connection connection;

public MapperProxy(Map<String, Mapper> mappers, Connection connection) {
this.mappers = mappers;
this.connection = connection;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 1、获取方法名
String methodName = method.getName();

// 2、获取方法所在的类名称
String className = method.getDeclaringClass().getName();

// 3、组合 key
String key = className + "." + methodName;

// 4、获取 mappers 中的 Mapper 对象
Mapper mapper = mappers.get(key);

// 5、判断是否有 mapper
if (mapper == null) {
throw new IllegalArgumentException("The args is wrong!!!");
}

// 6、调用工具类执行查询所有
return new Executor().selectList(mapper, connection);

}

}

第七步: 编写实现类 。首先在 mybatis 包 下新建 DefaultSqlSession 类 来实现 SqlSession 接口,该类的代码如下:

DefaultSqlSession.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package club.guoshizhan.mybatis;

import java.lang.reflect.Proxy;
import java.sql.Connection;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 10:52
* @Description: SqlSession 接口的实现类
*/
public class DefaultSqlSession implements SqlSession {

private Configuration cfg;
private Connection conn;

public DefaultSqlSession(Configuration cfg) {
this.cfg = cfg;
conn = DataSourceUtil.getConnection(cfg);
}

// 用于创建代理对象
@Override
public <T> T getMapper(Class<T> daoInterfaceClass) {
return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(), new Class[]{daoInterfaceClass}, new MapperProxy(cfg.getMappers(), conn));
}

// 用于释放资源
@Override
public void close() {
if (conn != null) {
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

}

接着在 mybatis 包 下新建 DefaultSqlSessionFactory 类 来实现 SqlSessionFactory 接口,该类的代码如下:

DefaultSqlSessionFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package club.guoshizhan.mybatis;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 10:55
* @Description: SqlSessionFactory 接口的实现类
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {

private Configuration cfg;

public DefaultSqlSessionFactory(Configuration cfg) {
this.cfg = cfg;
}

// 用于创建一个新的操作数据库对象
@Override
public SqlSession openSession() {
return new DefaultSqlSession(cfg);
}

}

最后继续编写 mybatis 包 下的 SqlSessionFactoryBuilder 类 【原先没有写完】,该类的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package club.guoshizhan.mybatis;

import java.io.InputStream;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 0:45
* @Description: 自定义 SqlSessionFactoryBuilder 类,用于创建一个 SqlSessionFactory 对象
*/
public class SqlSessionFactoryBuilder {

// 根据参数的字节输入流来构建一个 SqlSessionFactory 工厂
public SqlSessionFactory build(InputStream config) {
Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
return new DefaultSqlSessionFactory(cfg);
}

}

最后一步: 运行 MybatisTest 测试类中的 main 方法 ,结果如下图:


附加功能: 自定义 mybatis 并支持注解第一步: 修改 IUserDao 接口【就是加一个 Select 注解】 。代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;
import club.guoshizhan.mybatis.Select;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
@Select("select * from user")
List<User> findAll();

}

第二步: 修改 SqlMapConfig.xml 配置文件中的 mapper 属性,将其改为 class 。代码如下:

SqlMapConfig.xml
1
2
3
4
<!-- 06-使用注解的话,将 mapper 属性改为 class -->
<mappers>
<mapper class="club.guoshizhan.dao.IUserDao"/>
</mappers>

第三步: 新建 Select 注解类 。在 mybatis 包 下新建 Select 注解 。代码如下:

Select.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package club.guoshizhan.mybatis;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @Author: guoshizhan
* @Create: 2020/7/8 15:10
* @Description: 自定义的 Select 注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {

// 配置 SQL 语句
String value();

}

第四步: 修改 XMLConfigBuilder 类即把原先注释的部分放开【原先注解部分被注释掉了】 。代码如下:

XMLConfigBuilder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package club.guoshizhan.mybatis;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class XMLConfigBuilder {


/**
* 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方
* 使用的技术:dom4j + xpath
*/
public static Configuration loadConfiguration(InputStream config) {
try {
// 定义封装连接信息的配置对象(mybatis的配置对象)
Configuration cfg = new Configuration();

// 1.获取 SAXReader 对象
SAXReader reader = new SAXReader();
// 2.根据字节输入流获取 Document 对象
Document document = reader.read(config);
// 3.获取根节点
Element root = document.getRootElement();
// 4.使用 xpath 中选择指定节点的方式,获取所有 property 节点
List<Element> propertyElements = root.selectNodes("//property");
// 5.遍历节点
for (Element propertyElement : propertyElements) {
//判断节点是连接数据库的哪部分信息
//取出name属性的值
String name = propertyElement.attributeValue("name");
if ("driver".equals(name)) {
//表示驱动
//获取property标签value属性的值
String driver = propertyElement.attributeValue("value");
cfg.setDriver(driver);
}
if ("url".equals(name)) {
//表示连接字符串
//获取property标签value属性的值
String url = propertyElement.attributeValue("value");
cfg.setUrl(url);
}
if ("username".equals(name)) {
//表示用户名
//获取property标签value属性的值
String username = propertyElement.attributeValue("value");
cfg.setUsername(username);
}
if ("password".equals(name)) {
// 表示密码
// 获取 property 标签 value 属性的值
String password = propertyElement.attributeValue("value");
cfg.setPassword(password);
}
}
// 取出 mappers 中的所有 mapper 标签,判断他们使用了 resource 还是 class 属性
List<Element> mapperElements = root.selectNodes("//mappers/mapper");
// 遍历集合
for (Element mapperElement : mapperElements) {
// 判断 mapperElement 使用的是哪个属性
Attribute attribute = mapperElement.attribute("resource");
if (attribute != null) {
System.out.println("使用的是 XML ");
// 表示有 resource 属性,用的是XML
// 取出属性的值
String mapperPath = attribute.getValue(); // 获取属性的值"club/guoshizhan/dao/IUserDao.xml"
// 把映射配置文件的内容获取出来,封装成一个 map
Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
// 给 configuration 中的 mappers 赋值
cfg.setMappers(mappers);
} else {
System.out.println("使用的是注解");
//表示没有resource属性,用的是注解
//获取class属性的值
String daoClassPath = mapperElement.attributeValue("class");
//根据daoClassPath获取封装的必要信息
Map<String, Mapper> mappers = loadMapperAnnotation(daoClassPath);
//给configuration中的mappers赋值
cfg.setMappers(mappers);
}
}
// 返回 Configuration
return cfg;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
config.close();
} catch (Exception e) {
e.printStackTrace();
}
}

}

/**
* 根据传入的参数,解析XML,并且封装到Map中
*
* @param mapperPath 映射配置文件的位置
* @return map中包含了获取的唯一标识(key是由dao的全限定类名和方法名组成)
* 以及执行所需的必要信息(value是一个Mapper对象,里面存放的是执行的SQL语句和要封装的实体类全限定类名)
*/
private static Map<String, Mapper> loadMapperConfiguration(String mapperPath) throws IOException {
InputStream in = null;
try {
// 定义返回值对象
Map<String, Mapper> mappers = new HashMap<String, Mapper>();
// 1.根据路径获取字节输入流
in = Resources.getResourceAsStream(mapperPath);
// 2.根据字节输入流获取 Document 对象
SAXReader reader = new SAXReader();
Document document = reader.read(in);
// 3.获取根节点
Element root = document.getRootElement();
// 4.获取根节点的 namespace 属性取值
String namespace = root.attributeValue("namespace"); // 是组成 map 中 key 的部分
// 5.获取所有的 select 节点
List<Element> selectElements = root.selectNodes("//select");
// 6.遍历 select 节点集合
for (Element selectElement : selectElements) {
// 取出 id 属性的值【map 中 key 的部分】
String id = selectElement.attributeValue("id");
// 取出 resultType 属性的值 组成map中value的部分
String resultType = selectElement.attributeValue("resultType");
// 取出文本内容【map 中 value 的部分】
String queryString = selectElement.getText();
// 创建 Key
String key = namespace + "." + id;
// 创建 Value
Mapper mapper = new Mapper();
mapper.setQueryString(queryString);
mapper.setResultType(resultType);
// 把 key 和 value 存入 mappers 中
mappers.put(key, mapper);
}
return mappers;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
in.close();
}
}

/**
* 根据传入的参数,得到 dao 中所有被 select 注解标注的方法。
* 根据方法名称和类名,以及方法上注解 value 属性的值,组成 Mapper 的必要信息
*/
private static Map<String, Mapper> loadMapperAnnotation(String daoClassPath) throws Exception {
//定义返回值对象
Map<String, Mapper> mappers = new HashMap<String, Mapper>();

//1.得到dao接口的字节码对象
Class daoClass = Class.forName(daoClassPath);
//2.得到dao接口中的方法数组
Method[] methods = daoClass.getMethods();
//3.遍历Method数组
for (Method method : methods) {
//取出每一个方法,判断是否有select注解
boolean isAnnotated = method.isAnnotationPresent(Select.class);
if (isAnnotated) {
//创建Mapper对象
Mapper mapper = new Mapper();
//取出注解的value属性值
Select selectAnno = method.getAnnotation(Select.class);
String queryString = selectAnno.value();
mapper.setQueryString(queryString);
//获取当前方法的返回值,还要求必须带有泛型信息
Type type = method.getGenericReturnType();//List<User>
//判断type是不是参数化的类型
if (type instanceof ParameterizedType) {
//强转
ParameterizedType ptype = (ParameterizedType) type;
//得到参数化类型中的实际类型参数
Type[] types = ptype.getActualTypeArguments();
//取出第一个
Class domainClass = (Class) types[0];
//获取domainClass的类名
String resultType = domainClass.getName();
//给Mapper赋值
mapper.setResultType(resultType);
}
//组装key的信息
//获取方法的名称
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String key = className + "." + methodName;
//给map赋值
mappers.put(key, mapper);
}
}
return mappers;
}

}

最后一步: 运行 MybatisTest 测试类中的 main 方法 ,结果如下图:

mybatis 的基本使用

mybatis 的单表操作

这部分内容主要是用 mybatis 实现对数据库的 添加、删除、修改、查询 操作,使用在第一次搭建环境时候的代码 mybatis-01 (主要是省时间,就不再重新搭建环境了),在这个工程的代码上进行操作。


mybatis 保存操作

第一步: 编写 IUserDao 类,新加 保存用户 的方法。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
List<User> findAll();

// 保存用户
void saveUser(User user);

}

第二步: 编写 IUserDao.xml 文件,新加 保存用户 的 SQL 语句(使用 insert 标签的)。 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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">

<!-- 01-持久层映射配置中,mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名 -->
<mapper namespace="club.guoshizhan.dao.IUserDao">

<!-- 02-查询所有用户:resultType 很重要,是查询结果的封装位置,不写会报错,表示 mybatis 框架不知道把结果往哪里封装 -->
<!-- 03-属性 id 不能随便写,必须是接口中方法的名称 -->
<select id="findAll" resultType="club.guoshizhan.domain.User">
select * from user;
</select>

<!-- 保存用户。parameterType 是用于指定参数类型,必须加上,否则报错 -->
<!-- 注意:使用 # 号取值,里面的变量名字是和实体类里面的变量一一对应的 -->
<insert id="saveUser" parameterType="club.guoshizhan.domain.User">
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>
</mapper>

第三步: 编写 MybatisTest 测试类,然后执行 testSave 方法即可。 代码如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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.Date;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {

private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;

// // 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {

// 01-读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");

// 02-创建 SqlSessionFactory 工厂 ,SqlSessionFactory 是一个接口,不能 new 对象(因此需要使用它的实现类 SqlSessionFactoryBuilder)
factory = new SqlSessionFactoryBuilder().build(in);

// 03-使用工厂生产 SqlSession 对象
session = factory.openSession();

// 04-使用 SqlSession 创建 Dao 接口的代理对象
userDao = session.getMapper(IUserDao.class);

}

@Test
public void testFindAll() {
// 05-使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}

@Test
public void testSave() {
User user = new User();
user.setBirthday(new Date());
user.setAddress("BeiJing");
user.setSex("女");
user.setUsername("testSave");
userDao.saveUser(user);
session.commit(); // 提交事务,不写此行代码无法实现保存操作。我们可以把此行代码移动到 destory 方法中。
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destory() throws IOException {
// 06-关闭资源
session.close();
in.close();
}

}

如果没有加提交事务那行代码,那么虽然测试成功,但是数据库是查不到数据的。 结果如下图:

加上提交事务的代码,保存操作才可成功。 结果如下图:


附加内容,细节操作: 保存操作虽然成功了,但是我们无法获取到保存数据的 id 值。 如下图:

如果我一定要获取保存数据的 id 值,那怎么办呢? 这个简单,只需要修改 IUserDao.xml 文件中的 保存操作 部分即可。使用以下代码覆盖原先的 SQL 语句即可(使用 insert 标签的)。 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
<!-- 保存用户。parameterType 是用于指定参数类型,必须加上,否则报错 -->
<!-- 注意:使用 # 号取值,里面的变量名字是和实体类里面的变量一一对应的 -->
<insert id="saveUser" parameterType="club.guoshizhan.domain.User">
<!-- 插入操作完成后,获取到插入数据的 id 值。keyProperty 对应实体类中的 id。keyColumn 对应数据库的字段。order 代表何时执行此配置 -->
<selectKey keyProperty="id" keyColumn="id" resultType="Integer" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>

编写完配置之后,我们运行 testSave 方法,这个时候我们就获取到了保存数据的 id 值。 结果如下图:

TIPS: mybatis 的 保存操作 到此结束!!!

mybatis 修改操作

第一步: 编写 IUserDao 类,新加 修改用户 的方法。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
List<User> findAll();

// 保存用户
void saveUser(User user);

// 修改用户
void updateUser(User user);

}

第二步: 编写 IUserDao.xml 文件,新加 修改用户 的 SQL 语句(使用 update 标签的)。 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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">

<!-- 01-持久层映射配置中,mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名 -->
<mapper namespace="club.guoshizhan.dao.IUserDao">

<!-- 02-查询所有用户:resultType 很重要,是查询结果的封装位置,不写会报错,表示 mybatis 框架不知道把结果往哪里封装 -->
<!-- 03-属性 id 不能随便写,必须是接口中方法的名称 -->
<select id="findAll" resultType="club.guoshizhan.domain.User">
select * from user;
</select>

<!-- 保存用户。parameterType 是用于指定参数类型,必须加上,否则报错 -->
<!-- 注意:使用 # 号取值,里面的变量名字是和实体类里面的变量一一对应的 -->
<insert id="saveUser" parameterType="club.guoshizhan.domain.User">
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>

<!-- 修改用户 -->
<update id="updateUser" parameterType="club.guoshizhan.domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>

</mapper>

第三步: 编写 MybatisTest 测试类,然后执行 testUpdate 方法即可。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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.Date;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {

private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;

// // 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {

// 01-读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");

// 02-创建 SqlSessionFactory 工厂 ,SqlSessionFactory 是一个接口,不能 new 对象(因此需要使用它的实现类 SqlSessionFactoryBuilder)
factory = new SqlSessionFactoryBuilder().build(in);

// 03-使用工厂生产 SqlSession 对象
session = factory.openSession();

// 04-使用 SqlSession 创建 Dao 接口的代理对象
userDao = session.getMapper(IUserDao.class);

}

// 查询所有用户
@Test
public void testFindAll() {
// 05-使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}

// 保存用户
@Test
public void testSave() {
User user = new User();
user.setBirthday(new Date());
user.setAddress("BeiJing");
user.setSex("女");
user.setUsername("testSave");
userDao.saveUser(user);
}

// 修改用户
@Test
public void testUpdate() {
User user = new User();
user.setId(57); // 57 是刚刚 testSave 方法保存的数据的 id。每个人的不一样,需要修改成你数据库中有的数据
user.setAddress("ShangHai");
user.setBirthday(new Date());
user.setSex("女");
user.setUsername("testUpdate");
userDao.updateUser(user);
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destory() throws IOException {
// 06-关闭资源
session.commit(); // 提交事务,不写此行代码无法实现保存操作
session.close();
in.close();
}

}

执行 testUpdate 方法, 结果如下图:

TIPS: mybatis 的 修改操作 到此结束!!!

mybatis 删除操作

第一步: 编写 IUserDao 类,新加 根据 id 删除用户 的方法。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
List<User> findAll();

// 保存用户
void saveUser(User user);

// 修改用户
void updateUser(User user);

// 根据 id 删除用户
void deleteUser(Integer id);

}

第二步: 编写 IUserDao.xml 文件,新加 根据 id 删除用户 的 SQL 语句(使用 delete 标签的)。 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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">

<!-- 01-持久层映射配置中,mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名 -->
<mapper namespace="club.guoshizhan.dao.IUserDao">

<!-- 02-查询所有用户:resultType 很重要,是查询结果的封装位置,不写会报错,表示 mybatis 框架不知道把结果往哪里封装 -->
<!-- 03-属性 id 不能随便写,必须是接口中方法的名称 -->
<select id="findAll" resultType="club.guoshizhan.domain.User">
select * from user;
</select>

<!-- 保存用户。parameterType 是用于指定参数类型,必须加上,否则报错 -->
<!-- 注意:使用 # 号取值,里面的变量名字是和实体类里面的变量一一对应的 -->
<insert id="saveUser" parameterType="club.guoshizhan.domain.User">
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>

<!-- 修改用户 -->
<update id="updateUser" parameterType="club.guoshizhan.domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>

<!-- 根据 id 删除用户。注意:当只有一个参数时,占位符名称可以是任意值,我写的是#{id},也可写#{aa},都可以 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where id=#{id}
</delete>

</mapper>

第三步: 编写 MybatisTest 测试类,然后执行 testDelete 方法即可实现删除操作。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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.Date;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {

private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;

// // 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {

// 01-读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");

// 02-创建 SqlSessionFactory 工厂 ,SqlSessionFactory 是一个接口,不能 new 对象(因此需要使用它的实现类 SqlSessionFactoryBuilder)
factory = new SqlSessionFactoryBuilder().build(in);

// 03-使用工厂生产 SqlSession 对象
session = factory.openSession();

// 04-使用 SqlSession 创建 Dao 接口的代理对象
userDao = session.getMapper(IUserDao.class);

}

// 查询所有用户
@Test
public void testFindAll() {
// 05-使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}

// 保存用户
@Test
public void testSave() {
User user = new User();
user.setBirthday(new Date());
user.setAddress("BeiJing");
user.setSex("女");
user.setUsername("testSave");
userDao.saveUser(user);
}

// 修改用户
@Test
public void testUpdate() {
User user = new User();
user.setId(57); // 57 是刚刚 testSave 方法保存的数据的 id。每个人的不一样,需要修改成你数据库中有的数据
user.setAddress("ShangHai");
user.setBirthday(new Date());
user.setSex("女");
user.setUsername("testUpdate");
userDao.updateUser(user);
}

// 根据 id 删除用户
@Test
public void testDelete() {
userDao.deleteUser(57);
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destory() throws IOException {
// 06-关闭资源
session.commit(); // 提交事务,不写此行代码无法实现保存操作
session.close();
in.close();
}

}

TIPS: mybatis 的 删除操作 到此结束!!!

mybatis 查询操作

第一步: 编写 IUserDao 类,新加 根据 id 查询用户 的方法。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
List<User> findAll();

// 保存用户
void saveUser(User user);

// 修改用户
void updateUser(User user);

// 根据 id 删除用户
void deleteUser(Integer id);

// 根据 id 查询用户
User findById(Integer userId);

}

第二步: 编写 IUserDao.xml 文件,新加 根据 id 查询用户 的 SQL 语句(使用 select 标签的)。 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?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">

<!-- 01-持久层映射配置中,mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名 -->
<mapper namespace="club.guoshizhan.dao.IUserDao">

<!-- 02-查询所有用户:resultType 很重要,是查询结果的封装位置,不写会报错,表示 mybatis 框架不知道把结果往哪里封装 -->
<!-- 03-属性 id 不能随便写,必须是接口中方法的名称 -->
<select id="findAll" resultType="club.guoshizhan.domain.User">
select * from user;
</select>

<!-- 保存用户。parameterType 是用于指定参数类型,必须加上,否则报错 -->
<!-- 注意:使用 # 号取值,里面的变量名字是和实体类里面的变量一一对应的 -->
<insert id="saveUser" parameterType="club.guoshizhan.domain.User">
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>

<!-- 修改用户 -->
<update id="updateUser" parameterType="club.guoshizhan.domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>

<!-- 根据 id 删除用户。注意:当只有一个参数时,占位符名称可以是任意值,我写的是#{id},也可写#{aa},都可以 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where id=#{id}
</delete>

<!-- 根据 id 查询用户。注意:1、当只有一个参数时,占位符名称可以是任意值。2、需要指定结果集的返回值类型 -->
<select id="findById" parameterType="Integer" resultType="club.guoshizhan.domain.User">
select * from user where id=#{aaabbbccc}
</select>

</mapper>

第三步: 编写 MybatisTest 测试类,然后执行 testFindById 方法即可。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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.Date;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {

private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;

// // 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {

// 01-读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");

// 02-创建 SqlSessionFactory 工厂 ,SqlSessionFactory 是一个接口,不能 new 对象(因此需要使用它的实现类 SqlSessionFactoryBuilder)
factory = new SqlSessionFactoryBuilder().build(in);

// 03-使用工厂生产 SqlSession 对象
session = factory.openSession();

// 04-使用 SqlSession 创建 Dao 接口的代理对象
userDao = session.getMapper(IUserDao.class);

}

// 查询所有用户
@Test
public void testFindAll() {
// 05-使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}

// 保存用户
@Test
public void testSave() {
User user = new User();
user.setBirthday(new Date());
user.setAddress("BeiJing");
user.setSex("女");
user.setUsername("testSave");
userDao.saveUser(user);
}

// 修改用户
@Test
public void testUpdate() {
User user = new User();
user.setId(57); // 57 是刚刚 testSave 方法保存的数据的 id。每个人的不一样,需要修改成你数据库中有的数据
user.setAddress("ShangHai");
user.setBirthday(new Date());
user.setSex("女");
user.setUsername("testUpdate");
userDao.updateUser(user);
}

// 根据 id 删除用户
@Test
public void testDelete() {
userDao.deleteUser(57);
}

// 根据 id 查询用户
@Test
public void testFindById() {
User user = userDao.findById(55);
System.out.println(user);
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destory() throws IOException {
// 06-关闭资源
session.commit(); // 提交事务,不写此行代码无法实现保存操作
session.close();
in.close();
}

}

执行 testFindById 方法便可查询到结果了,这里就不截图了。那么问题来了,如果我需要模糊查询改怎么做呢?安排!!! 第一步: 编写 IUserDao 类,新加 模糊查询用户 的方法。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
List<User> findAll();

// 保存用户
void saveUser(User user);

// 修改用户
void updateUser(User user);

// 根据 id 删除用户
void deleteUser(Integer id);

// 根据 id 查询用户
User findById(Integer userId);

// 模糊查询用户
List<User> findByName(String username);

}

第二步: 编写 IUserDao.xml 文件,新加 模糊查询用户 的 SQL 语句(使用 select 标签的)。 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?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">

<!-- 01-持久层映射配置中,mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名 -->
<mapper namespace="club.guoshizhan.dao.IUserDao">

<!-- 02-查询所有用户:resultType 很重要,是查询结果的封装位置,不写会报错,表示 mybatis 框架不知道把结果往哪里封装 -->
<!-- 03-属性 id 不能随便写,必须是接口中方法的名称 -->
<select id="findAll" resultType="club.guoshizhan.domain.User">
select * from user;
</select>

<!-- 保存用户。parameterType 是用于指定参数类型,必须加上,否则报错 -->
<!-- 注意:使用 # 号取值,里面的变量名字是和实体类里面的变量一一对应的 -->
<insert id="saveUser" parameterType="club.guoshizhan.domain.User">
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>

<!-- 修改用户 -->
<update id="updateUser" parameterType="club.guoshizhan.domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>

<!-- 根据 id 删除用户。注意:当只有一个参数时,占位符名称可以是任意值,我写的是#{id},也可写#{aa},都可以 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where id=#{id}
</delete>

<!-- 根据 id 查询用户。注意:1、当只有一个参数时,占位符名称可以是任意值。2、需要指定结果集的返回值类型 -->
<select id="findById" parameterType="Integer" resultType="club.guoshizhan.domain.User">
select * from user where id=#{aaabbbccc}
</select>

<!-- 模糊查询查询用户 -->
<select id="findByName" parameterType="String" resultType="club.guoshizhan.domain.User">
-- 如果使用下列语句实现模糊查询,那么需要在测试类的代码中添加上 % 符号,否则查询不出来。
-- 注意:此处执行的 SQL 语句用的是占位符方式(带有预处理方式),可以防止 SQL 注入,更安全
select * from user where username like #{name}
-- 如果使用下列语句实现模糊查询,那么在测试类的代码中就不需要加上 % 了。注意:此处执行的 SQL 语句用的是拼接方式,不安全
-- 下列语句中的 value 是固定的,不能写成其他的名字,这个是在源码规定好的,详情请参考 TextSqlNode 类
-- select * from user where username like '%${value}%'
</select>

</mapper>

第三步: 编写 MybatisTest 测试类,然后执行 testFindByName 方法即可。 代码如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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.Date;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {

private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;

// // 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {

// 01-读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");

// 02-创建 SqlSessionFactory 工厂 ,SqlSessionFactory 是一个接口,不能 new 对象(因此需要使用它的实现类 SqlSessionFactoryBuilder)
factory = new SqlSessionFactoryBuilder().build(in);

// 03-使用工厂生产 SqlSession 对象
session = factory.openSession();

// 04-使用 SqlSession 创建 Dao 接口的代理对象
userDao = session.getMapper(IUserDao.class);

}

// 查询所有用户
@Test
public void testFindAll() {
// 05-使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}

// 保存用户
@Test
public void testSave() {
User user = new User();
user.setBirthday(new Date());
user.setAddress("BeiJing");
user.setSex("女");
user.setUsername("testSave");
userDao.saveUser(user);
}

// 修改用户
@Test
public void testUpdate() {
User user = new User();
user.setId(57); // 57 是刚刚 testSave 方法保存的数据的 id。每个人的不一样,需要修改成你数据库中有的数据
user.setAddress("ShangHai");
user.setBirthday(new Date());
user.setSex("女");
user.setUsername("testUpdate");
userDao.updateUser(user);
}

// 根据 id 删除用户
@Test
public void testDelete() {
userDao.deleteUser(57);
}

// 根据 id 查询用户
@Test
public void testFindById() {
User user = userDao.findById(55);
System.out.println(user);
}

// 模糊查询用户
@Test
public void testFindByName() {
// 此处需要加上 % ,否则查询无结果
List<User> users = userDao.findByName("%王%");
for (User user : users) {
System.out.println(user);
}
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destory() throws IOException {
// 06-关闭资源
session.commit(); // 提交事务,不写此行代码无法实现保存操作
session.close();
in.close();
}

}

TIPS: mybatis 的 查询操作 到此结束!!!

mybatis 的参数和返回值

mybatis 的参数

我们先看如下代码,了解一下 mybatis 的参数位置:

1
2
3
<update id="updateUser" parameterType="club.guoshizhan.domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>

在上述代码中,parameterType 就是 mybatis 的参数类型,它的值有多种类型。 具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ parameterType(输入类型),其输入类型如下:
+ 基本类型
+ 引用类型
+ 实体类型(POJO 实体类)
+ POJO 的包装类

+++ OGNL 表达式的全称为: Object Graphic Navigation Language,翻译一下就是:对象图导航语言,
+ 它是通过对象的取值方法来获取数据。在写法上把 get 给省略了。
+ 比如:我们获取用户的名称
+ 类中的写法:user.getUsername();
+ OGNL 表达式写法:user.username
+ mybatis 中为什么能直接写 username,而不用 user. 呢?因为在 parameterType 中已经提供了属性所属的类,所以此时不需要写对象名

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

开发中通过 POJO 传递查询条件,查询条件是综合的查询条件, 不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以 使用 POJO 包装对象(POJO 类中包含 POJO)传递输入参数。 接下来我们搞定一个需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。


第一步: 在 domain 包下新建 QueryVo 类。 代码如下:

QueryVo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package club.guoshizhan.domain;

/**
* @Author: guoshizhan
* @Create: 2020/8/22 20:34
* @Description: QueryVo 实体类(此类中还包含了 User 实体类)
*/
public class QueryVo {

private User user;

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

}

第二步: 编写 IUserDao 类,新加 QueryVo 中的条件查询用户 的方法。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package club.guoshizhan.dao;

import club.guoshizhan.domain.QueryVo;
import club.guoshizhan.domain.User;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
List<User> findAll();

// 保存用户
void saveUser(User user);

// 修改用户
void updateUser(User user);

// 根据 id 删除用户
void deleteUser(Integer id);

// 根据 id 查询用户
User findById(Integer userId);

// 模糊查询用户
List<User> findByName(String username);

// QueryVo 中的条件查询用户
List<User> findByVo(QueryVo vo);

}

第三步: 把以下代码加入到 IUserDao.xml 文件的 mapper 标签内。 代码如下:

IUserDao.xml
1
2
3
4
<!-- QueryVo 中的条件查询用户:注意 mybatis 的参数类型和 SQL 语句中的查询条件的写法 -->
<select id="findByVo" parameterType="club.guoshizhan.domain.QueryVo" resultType="club.guoshizhan.domain.User">
select * from user where username like #{user.username}
</select>

第四步: 把以下的测试代码加入到 MybatisTest 类中,然后运行即可。 代码如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
// QueryVo 中的条件查询用户
@Test
public void testFindByVo() {
QueryVo queryVo = new QueryVo();
User user = new User();
user.setUsername("%王%");
queryVo.setUser(user);

List<User> users = userDao.findByVo(queryVo);
for (User user1 : users) {
System.out.println(user1);
}
}

第五步: 执行 testFindByVo 方法。 结果如下:

TIPS: mybatis 的参数 到此结束!!!

mybatis 的返回值

我们先看如下代码,了解一下 mybatis 的返回值位置:

1
2
3
<select id="findAll" resultType="club.guoshizhan.domain.User">
select * from user;
</select>

在上述代码中,resultType 就是 mybatis 的返回值类型,它的值有多种类型。 具体如下:

1
2
3
4
5
6
7
8
9
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ resultType(输入类型),其输入类型如下:
+ 基本类型
+ 引用类型
+ 实体类型(POJO 实体类)
+ 实体类型列表(说白了就是集合。例如 List<User>)

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

在我们编写实体类 User 时,我们需要把 User 类的变量名和数据库中的字段名保持一致。那么问题来了,如果不一致,会有什么后果呢? 带着这个问题,我们把实体类 User 做一下修改(每个变量名加上前缀 user)。代码如下:

User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package club.guoshizhan.domain;

import java.io.Serializable;
import java.util.Date;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:52
* @Description: User 实体类
*/
public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;

public Integer getUserId() {
return userId;
}

public void setUserId(Integer userId) {
this.userId = userId;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public Date getUserBirthday() {
return userBirthday;
}

public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}

public String getUserSex() {
return userSex;
}

public void setUserSex(String userSex) {
this.userSex = userSex;
}

public String getUserAddress() {
return userAddress;
}

public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}

@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userBirthday=" + userBirthday +
", userSex='" + userSex + '\'' +
", userAddress='" + userAddress + '\'' +
'}';
}
}

当我们修改完之后,测试类就报错了。这个没有关系,修改一下就好了(自行修改),这个错误是可以看见的错误。那么不可见的错误出现在哪呢?它在 IUserDao.xml 文件中。以下将以文件中的部分代码举例。 如下:

1
2
3
4
5
6
7
8
9
<!-- 注意:使用 # 号取值,里面的变量名字是和实体类里面的变量一一对应的,所以实体类变量名改变了,就无法取到值了 -->
<insert id="saveUser" parameterType="club.guoshizhan.domain.User">
<!-- keyProperty 对应实体类中的属性,实体类 id 变成了 userId ,所以此处也要修改。-->
<!-- keyColumn 对应着数据库的字段。order 代表何时执行此配置 -->
<selectKey keyProperty="id" keyColumn="id" resultType="Integer" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>

上述介绍完之后,我们就修改一下 IUserDao.xml 文件(修改部分,其他照着例子完成修改)。 修改如下:

1
2
3
4
5
6
7
8
<insert id="saveUser" parameterType="club.guoshizhan.domain.User">
<!-- 此处 keyProperty 修改了 -->
<selectKey keyProperty="userId" keyColumn="id" resultType="Integer" order="AFTER">
select last_insert_id();
</selectKey>
<!-- 此处 OGNL 表达式都进行了修改 -->
insert into user(username,birthday,sex,address) values (#{userName},#{userBirthday},#{userSex},#{userAddress})
</insert>

上述做了局部修改,其他修改都差不多,我就不过多讲述。修改完成后去测试类进行测试,发现了问题: 增删改的测试没有问题。查询出问题。查询结果不能封装到对象里面,只有 userName 一个属性可以封装成功,其他属性都为 null。 接下来就解释原因,并提供解决方案。


问题的原因: 之所以 userName 有值,其他属性没有值,原因有两个:第一,MySQL 在 windows 中不区分大小写(如果在 Linux 中,userName 也为 null,因为 Linux 中 MySQL 严格区分大小写)。第二,实体类属性和数据库字段需要对的上才可封装。 因为第一个原因,所以 User 中的 userName 属性和数据库中的 username 字段可以认为是一样的,所以 userName 就有值,而其他的属性对不上,所以没有值。


问题的解决方案一: 我们可以通过起别名的方式进行封装(在 IUserDao.xml 中操作)。 部分代码如下:

1
2
3
<select id="findAll" resultType="club.guoshizhan.domain.User">
select id as userId,username As userName,birthday as userBirthday,address as userAddress,sex as userSex from user;
</select>

问题的解决方案二: 我们还可以通过配置的方式进行封装(在 IUserDao.xml 中操作)。 部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 配置数据库字段和实体类属性相对应。以下所有代码都写在 mapper 标签里面 -->
<!-- resultMap 是唯一标志,名字可以随便写。type 对应的是实体类的类型 -->
<resultMap id="userMap" type="club.guoshizhan.domain.User">
<!-- 主键字段的对应。property 对应实体类 id 属性,column 对应数据库 id 字段 -->
<id property="userId" column="id"></id>
<!-- 非主键字段的对应。注意:property 对应 Java 中的属性,是严格区分大小写的 -->
<result property="userName" column="username"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
<result property="userAddress" column="address"></result>
</resultMap>

<!-- 这里需要使用 resultMap 封装结果集,它的值是 resultMap 标签中的 id 属性的值 -->
<select id="findAll" resultMap="userMap">
select * from user;
</select>

上述两种解决方案都可以解决封装不成功的问题 (使用上述任意一种方案,再次执行查询,那就不会再出现封装不上的情况了)。这两种方案各有利弊: 配置的方式效率不如起别名的高,但是对于代码的维护和修改,配置的方式是更方便的。

TIPS: mybatis 的返回值 到此结束!!!

mybatis 自定义 dao 实现类

上述的 mybatis 的修改操作完成之后,把代码都复原一下(复原到 mybatis 的返回值这一小结之前),这便于以下的操作。 我们知道,mybatis 是使用代理 dao 的方式实现增删改查操作,那如果不使用这种方式,而是我们自己写实现类,那该怎么做呢? 那就安排一下吧!!!


第一步: 查看我们的 IUserDao 接口。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
List<User> findAll();

// 保存用户
void saveUser(User user);

// 修改用户
void updateUser(User user);

// 根据 id 删除用户
void deleteUser(Integer id);

// 根据 id 查询用户
User findById(Integer userId);

// 模糊查询用户
List<User> findByName(String username);

}

第二步: 在 dao 包下新建 IUserDao 接口的实现类 UserDaoImpl ,这个就是我们的自定义实现类。 代码如下:

UserDaoImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/8/22 23:20
* @Description: 自定义 IUserDao 的实现类
*/
public class UserDaoImpl implements IUserDao {

private SqlSessionFactory factory;

public UserDaoImpl(SqlSessionFactory factory) {
this.factory = factory;
}

@Override
public List<User> findAll() {
// 01-根据 factory 获取 SqlSession 对象
SqlSession session = factory.openSession();
// 02-调用方法实现查询所有。参数是 IUserDao.xml 中的 namespace 的值 + 方法名称,相当于获取配置信息的 key
List<User> user = session.selectList("club.guoshizhan.dao.IUserDao.findAll");
// 03-释放资源
session.close();
return user;
}

@Override
public void saveUser(User user) {
// 01-根据 factory 获取 SqlSession 对象
SqlSession session = factory.openSession();
// 02-调用方法实现保存操作。注意:参数 user 别忘记了,忘了的话会报错:username 不能为空
session.insert("club.guoshizhan.dao.IUserDao.saveUser", user);
// 03-提交事务
session.commit();
// 04-释放资源
session.close();
}

@Override
public void updateUser(User user) {
// 01-根据 factory 获取 SqlSession 对象
SqlSession session = factory.openSession();
// 02-调用方法实现修改操作。注意:参数 user 别忘记了,忘了的话会报错:username 不能为空
session.update("club.guoshizhan.dao.IUserDao.updateUser", user);
// 03-提交事务
session.commit();
// 04-释放资源
session.close();
}

@Override
public void deleteUser(Integer id) {
// 01-根据 factory 获取 SqlSession 对象
SqlSession session = factory.openSession();
// 02-调用方法实现删除操作。调用 delete 和 update 方法都可以实现
session.delete("club.guoshizhan.dao.IUserDao.deleteUser", id);
// 03-提交事务
session.commit();
// 04-释放资源
session.close();
}

@Override
public User findById(Integer userId) {
// 01-根据 factory 获取 SqlSession 对象
SqlSession session = factory.openSession();
// 02-调用方法实现通过 id 查询一个用户
User user = session.selectOne("club.guoshizhan.dao.IUserDao.findById", userId);
// 03-释放资源
session.close();
return user;
}

@Override
public List<User> findByName(String username) {
// 01-根据 factory 获取 SqlSession 对象
SqlSession session = factory.openSession();
// 02-调用方法实现模糊查询
List<User> user = session.selectList("club.guoshizhan.dao.IUserDao.findByName", username);
// 03-释放资源
session.close();
return user;
}

}

第三步: 查看我们的配置文件 IUserDao.xml ,看看是否编写正确 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?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">

<!-- 01-持久层映射配置中,mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名 -->
<mapper namespace="club.guoshizhan.dao.IUserDao">

<!-- 02-查询所有用户:resultType 很重要,是查询结果的封装位置,不写会报错,表示 mybatis 框架不知道把结果往哪里封装 -->
<!-- 03-属性 id 不能随便写,必须是接口中方法的名称 -->
<select id="findAll" resultType="club.guoshizhan.domain.User">
select * from user;
</select>

<!-- 保存用户。parameterType 是用于指定参数类型,必须加上,否则报错 -->
<!-- 注意:使用 # 号取值,里面的变量名字是和实体类里面的变量一一对应的 -->
<insert id="saveUser" parameterType="club.guoshizhan.domain.User">
<!-- 插入操作完成后,获取到插入数据的 id 值。keyColumn 对应数据库的字段。order 代表何时执行此配置 -->
<selectKey keyProperty="id" keyColumn="id" resultType="Integer" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>

<!-- 修改用户 -->
<update id="updateUser" parameterType="club.guoshizhan.domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>

<!-- 根据 id 删除用户。注意:当只有一个参数时,占位符名称可以是任意值,我写的是#{id},也可写#{aa},都可以 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where id=#{id}
</delete>

<!-- 根据 id 查询用户。注意:1、当只有一个参数时,占位符名称可以是任意值。2、需要指定结果集的返回值类型 -->
<select id="findById" parameterType="Integer" resultType="club.guoshizhan.domain.User">
select * from user where id=#{aaabbbccc}
</select>

<!-- 模糊查询用户 -->
<select id="findByName" parameterType="String" resultType="club.guoshizhan.domain.User">
-- 如果使用下列语句实现模糊查询,那么 % 需要在测试类的代码中添加上,否则查询不出来
-- 注意:此处执行的 SQL 语句用的是占位符方式,可以防止 SQL 注入,更安全
select * from user where username like #{name}
</select>

</mapper>

第四步: 编写我们的 MybatisTest 测试类(此类在 init 方法和 destory 方法中稍有改动)。 代码如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.dao.UserDaoImpl;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
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.Date;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {

private InputStream in;
private IUserDao userDao;

// 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {

// 01-读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");

// 02-创建 SqlSessionFactory 工厂 ,SqlSessionFactory 是一个接口,不能 new 对象(因此需要使用它的实现类 SqlSessionFactoryBuilder)
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

// 03-使用工厂对象创建 dao 对象
userDao = new UserDaoImpl(factory);

}

// 查询所有用户
@Test
public void testFindAll() {
// 05-使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}

// 保存用户
@Test
public void testSave() {
User user = new User();
user.setBirthday(new Date());
user.setAddress("Java");
user.setSex("女");
user.setUsername("testSave");

System.out.println("保存操作之前" + user);
userDao.saveUser(user);
System.out.println("保存操作之后" + user);
}

// 修改用户
@Test
public void testUpdate() {
User user = new User();
user.setId(67);
user.setAddress("ShangHai");
user.setBirthday(new Date());
user.setSex("女");
user.setUsername("testUpdate");
userDao.updateUser(user);
}

// 根据 id 删除用户
@Test
public void testDelete() {
userDao.deleteUser(68);
}

// 根据 id 查询用户
@Test
public void testFindById() {
User user = userDao.findById(55);
System.out.println(user);
}

// 模糊查询用户
@Test
public void testFindByName() {
// 此处需要加上 % ,否则查询无结果
List<User> users = userDao.findByName("%王%");
for (User user : users) {
System.out.println(user);
}
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destory() throws IOException {
// 06-关闭资源
in.close();
}

}

TIPS: 运行每一个方法,结果都是一样的(我的没有报错,和原来完全一样),这里就不截图了。自定义 dao 实现类 到此结束!!

mybatis 的基本配置

properties 标签

properties 标签主要用于加载配置(例如数据库的配置文件),使用起来更为灵活。 那么先看一下我们的 mybatis 的配置文件,等会使用 propertis 标签 改造一下。mybatis 的配置文件(SqlMapConfig.xml) 的代码如下:

SqlMapConfig.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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">

<!-- 01-mybatis 的主配置文件 -->
<configuration>

<!-- 02-配置 mybatis 的环境,default 属性的值随便写,写完之后,下面 id 的值必须和 default 的值一样 -->
<environments default="mysql">
<!-- 03-配置 mysql 的环境 -->
<environment id="mysql">
<!-- 04-配置事务的类型,type 先写 JDBC ,其他的以后说 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 05-配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>

<!-- 06-指定 mybatis 映射配置的位置,即从 resources/club/guoshizhan/dao/ 包下找 IUserDao.xml 文件 -->
<mappers>
<mapper resource="club/guoshizhan/dao/IUserDao.xml"/>
</mappers>

</configuration>

看完上述配置之后,我们使用 properties 标签 改造一下(用法和详情都写在注释中了)。 代码如下:

SqlMapConfig.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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">

<!-- 01-mybatis 的主配置文件 -->
<configuration>

<!-- 配置 properties,方便下面的取值(此时这样写多此一举,一般都是引入外部配置文件,等下讲) -->
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>

<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!-- ${} 里面的值取的就是上面 properties 标签中 name 属性的值 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

<mappers>
<mapper resource="club/guoshizhan/dao/IUserDao.xml"/>
</mappers>

</configuration>

配置完后,我们去测试类执行各种方法,也是一样可以的。但是,有没有发现, properties 标签不是多此一举吗?的确,如果像上述配置那样,那就是多此一举。 所以,我们还需要改造一下,接着往下看!!!


第一步: resources 目录下新建 jdbcConfig.properties 数据库配置文件,把以下代码复制到里面:

jdbcConfig.properties
1
2
3
4
5
# 这些是数据库的连接信息,写自己的,此处仅供参考
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy_mybatis
jdbc.username=root
jdbc.password=root

第二步: 重新编写 SqlMapConfig.xml 配置文件(properties 标签的用法都在里面)。 代码如下:

SqlMapConfig.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?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">

<!-- 01-mybatis 的主配置文件 -->
<configuration>

<!-- 可以在 properties 标签内部配置数据库的信息,也可以通过属性引用外部配置文件的信息 -->
<!-- resource 属性(常用):用于指定配置文件的位置,按照类路径的写法来写,且必须存在于类路径下 -->
<!-- url 属性(不常用):需要按照 URL(Uniform Resources Locator 统一资源定位符) 写法来写
它可以唯一标识一个资源的位置。它的写法:协议+主机+端口+URI 例如:http://localhost:8080/hello
URI(Uniform Resources Identifier): 统一资源标识符,她在应用中可以唯一标识一个资源
-->
<properties resource="jdbcConfig.properties">
<!-- 这是使用 resource 的方式指定配置文件,配置文件存在于类路径中,即 resources 目录下 -->
<!-- 运行测试类可能报错:Could not find resource jdbcConfig.properties ,解决方法:选中 resources 目录,点击右键 -->
<!-- 选择 Make Directory as 选项,把 resources 改为 Source Root ,然后改回 Resources 即可 -->
</properties>

<!--<properties url="file:///c:/users/guoshizhan/Desktop/jdbcConfig.properties">
&lt;!&ndash; 此处使用 url 的方式,把外部的配置文件加载进来。为了方便演示,我们把 jdbcConfig.properties 文件复制到桌面 &ndash;&gt;
&lt;!&ndash; 然后把上述 properties 标签注释掉,再打开此处的 properties 标签。url 格式必须和上面的一样,否则报错(只需要改用户名) &ndash;&gt;
</properties>-->

<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!-- 此处别忘了,${} 里面的值要和配置文件的 key 要一致 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

<mappers>
<mapper resource="club/guoshizhan/dao/IUserDao.xml"/>
</mappers>

</configuration>

TIPS: 最后,我们只需要执行测试类进行测试即可。properties 标签 到此结束!!!

typeAliases 标签

typeAliases 标签主要用于起别名,方便我们的代码编写,从而提高效率。 我们先看如下代码:

1
2
3
4
5
6
7
8
9
<!-- 此处的参数类型为:club.guoshizhan.domain.User ,那我能简写成 User 吗。没有起别名之前,是不行的 -->
<update id="updateUser" parameterType="club.guoshizhan.domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>

<!-- 此处的参数类型为:Integer ,我们也可以写 int ,java.lang.Integer 。那这是为什么呢?这是因为 mybatis 框架事先给这些常用的类型起好了别名 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where id=#{id}
</delete>

那么我们想给的实体类指定别名怎么做? 安排(具体操作和详情写于注释中)!!! 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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">

<!-- 01-mybatis 的主配置文件 -->
<configuration>

<properties resource="jdbcConfig.properties"></properties>

<!-- 使用 typeAliases 标签配置别名 -->
<typeAliases>
<!-- typeAlias 标签用于起别名,type 属性用于指定实体类的全限定类名, alias 属性用于指定别名 -->
<!-- 指定的别名不区分大小写。User、user、UsEr、uSER 等等都是一样的 -->
<typeAlias type="club.guoshizhan.domain.User" alias="User"></typeAlias>
</typeAliases>

<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

<mappers>
<mapper resource="club/guoshizhan/dao/IUserDao.xml"/>
</mappers>

</configuration>

配置完成之后,我们再编写我们的 IUserDao.xml 的部分代码(起完别名之后,参数类型可以用别名,也可以不用)。 如下:

1
2
3
4
<!-- 原先我们必须写 club.guoshizhan.domain.User ,起别名之后,就可以简写成 User 了(不区分大小写)  -->
<update id="updateUser" parameterType="User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>

然后我们去测试类进行测试即可(放心,没问题)。 那么问题又来了,如果我有非常非常多的实体类,一个一个的注册,那岂不是超级耗时间? 所以,它还有另一种起别名的方式(使用 package 标签,在 mappers 的标签中也可以使用 package 标签)。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?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">

<!-- 01-mybatis 的主配置文件 -->
<configuration>

<properties resource="jdbcConfig.properties"></properties>

<!-- 使用 typeAliases 标签配置别名 -->
<typeAliases>
<!-- typeAlias 标签用于起别名,type 属性用于指定实体类的全限定类名, alias 属性用于指定别名 -->
<!-- 指定的别名不区分大小写。User、user、UsEr、uSER 等等都是一样的 -->
<!--<typeAlias type="club.guoshizhan.domain.User" alias="User"></typeAlias>-->

<!-- 这个是第二种起别名的方式,使用 package 标签,具体结介绍如下 -->
<!-- package 标签用于指定要配置别名的包,当指定之后,该包下的所有实体类都会注册别名,并且类名就是别名,且不区分大小写 -->
<package name="club.guoshizhan.domain"></package>
</typeAliases>

<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

<mappers>
<!--<mapper resource="club/guoshizhan/dao/IUserDao.xml"/>-->

<!-- 此处 package 标签用于指定 dao 接口所在的包,当指定完成之后,不需要再写 resource 或者 class 属性了 -->
<package name="club.guoshizhan.dao"></package>
</mappers>

</configuration>

TIPS: 最后,我们只需要执行测试类进行测试即可。typeAliases 标签 到此结束!!!

mybatis 连接池

连接池概念和优点

连接池概念: 连接池其实就是一个容器(集合),存放数据库连接的容器。 当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。 草图如下:

连接池的好处:
1、节约资源(可重复利用) 2、用户访问高效(减少了我们获取连接所消耗的时间)

mybatis 连接池的三种配置

mybatis 连接池 提供了 3 种方式的配置, 具体详情如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ mybatis 连接池配置的位置:
+ 在主配置文件 SqlMapConfig.xml 中的 dataSource 标签中,type 属性就是表示采用何种连接池方式。

+++ type 属性的取值如下:
+ POOLED 采用传统的 javax.sql.DataSource 规范中的连接池,mybatis 中有针对规范的实现
+ UNPOOLED 采用传统的获取连接的方式,虽然也实现 Javax.sql.DataSource 接口,但是并没有使用池的思想。
+ JNDI 采用服务器提供的 JNDI 技术实现,来获取 DataSource 对象,不同的服务器所能拿到 DataSource 是不一样。

+++ 注意:如果不是 web 或者 maven 的 war 工程,JNDI 是不能使用的。我们使用的是 tomcat 服务器,采用连接池就是 dbcp 连接池。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

接下来,我们使用 POOLED 方式进行相关测试(就是对 SqlMapConfig.xml 中的 dataSource 标签进行修改)。 代码如下:

1
2
3
4
5
6
7
8
<!-- 使用 POOLED 的方式。然后去测试类执行查询所有的方法。 -->
<!-- 执行成功之后,把 POOLED 改为 UNPOOLED ,然后再执行一次查询所有的方法 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>

经过上述操作后,我们来对比一下上述两种方式的不同。 如下图:

POOLED 方式 的源码分析 如下图:


TIPS: 一般情况下,我们都是使用 POOLED 的配置方式。

mybatis 中的事务

学习 MySQL 的时候就已经学过事务了,所以这里只是简单的概述一下。 介绍如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ 学习事务需要知道的几个问题:
+ 1、什么是事务
+ 2、事务的四大特性 ACID
+ 3、不考虑隔离性会产生的 3 个问题
+ 4、解决办法:四种隔离级别

+++ 注意:它是通过 sqlsession 对象的 commit 方法和 rollback 方法实现事务的提交和回滚
+++ 根据 factory 获取 SqlSession 对象时,我们可以设置自动提交事务,只要在里面写上 true 即可
+ SqlSession session = factory.openSession(true);

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

mybatis 的动态 SQL

动态 SQL 语句都是和查询相关的。 那么为什么需要动态 SQL 呢?我举个例子,我们去淘宝买东西的时候,是不是可以根据条件来购买我们需要的东西。比如说:价格区间、销量、颜色等等。这些条件便需要我们的动态 SQL 去完成(以后学习 mybatisPlus 的时候,wrapper 也可以实现条件查询)。所以,动态 SQL 是用来做 多条件查询 的。接着往下看!!!


if 标签

if 标签 根据实体类的不同取值,使用不同的 SQL 语句来进行查询,从而实现了多条件的综合查询。 比如在 id 如果不为空时可以根据 id 查询。那么我们就来操作一波吧!!!


第一步: 编写 IUserDao 类,新加 根据传入的条件进行查询 的方法。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
List<User> findAll();

// 根据 id 查询用户
User findById(Integer userId);

// 模糊查询用户
List<User> findByName(String username);

// 根据传入的条件进行查询。可能有用户名,可能有性别,也可能什么都有,也可能什么都没有
List<User> findByCondition(User user);

}

第二步: 编写 IUserDaoImpl 类,重写 根据传入的条件进行查询 的方法。 代码如下:

IUserDaoImpl.java
1
2
3
4
5
6
7
8
9
10
@Override
public List<User> findByCondition(User user) {
// 01-根据 factory 获取 SqlSession 对象
SqlSession session = factory.openSession();
// 02-调用方法实现模糊查询
List<User> list = session.selectList("club.guoshizhan.dao.IUserDao.findByCondition", user);
// 03-释放资源
session.close();
return list;
}

第三步: 编写 IUserDao.xml 配置文件,新加如下代码(里面就是 if 标签 的用法)。 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
<!-- 根据传入的条件进行查询 -->
<select id="findByCondition" parameterType="user" resultType="club.guoshizhan.domain.User">
select * from user where 1=1
<!-- 此处使用了 if 标签。注意:OGNL 表达式里面的 username 和 test 里面的 username 要和实体类中的属性相对应。-->
<if test="username != null">
and username like #{username}
</if>
<if test="sex != null">
and sex = #{sex}
</if>
</select>

第四步: 编写测试类代码,然后执行相应的方法即可。 代码如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
// 根据传入的条件进行查询
@Test
public void testFindByCondition() {
User user = new User();
user.setUsername("%王%");
user.setSex("男");

List<User> users = userDao.findByCondition(user);
for (User user1 : users) {
System.out.println(user1);
}
}

执行测试代码中的 testFindByCondition 方法, 截取的部分结果如下(注意结果中的 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
2020-08-23 22:34:07,143 308    [           main] DEBUG .apache.ibatis.io.ResolverUtil  - Checking to see if class club.guoshizhan.dao.IUserDao matches criteria [is assignable to Object]
2020-08-23 22:34:07,146 311 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.UserDaoImpl matches criteria [is assignable to Object]
2020-08-23 22:34:07,426 591 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-23 22:34:07,701 866 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@ac3dc3]
2020-08-23 22:34:07,708 873 [ main] DEBUG n.dao.IUserDao.findByCondition - ==> Preparing: select * from user where 1=1 and username like ? and sex = ?
2020-08-23 22:34:07,739 904 [ main] DEBUG n.dao.IUserDao.findByCondition - ==> Parameters: %王%(String), 男(String)
2020-08-23 22:34:07,765 930 [ main] DEBUG n.dao.IUserDao.findByCondition - <== Total: 2
2020-08-23 22:34:07,765 930 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@ac3dc3]
2020-08-23 22:34:07,765 930 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@ac3dc3]
User{id=43, username='大王', birthday=Tue Nov 12 10:19:13 CST 2019, sex='男', address='Los Angeles'}
User{id=46, username='老王', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='北京'}

where 标签

为了简化上面 where 1=1 的条件拼装,我们可以采用 where 标签 来简化开发。 简化后的代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 根据传入的条件进行查询 -->
<select id="findByCondition" parameterType="user" resultType="club.guoshizhan.domain.User">
select * from user
/* 此处使用了 where 标签 */
<where>
<if test="username != null">
and username like #{username}
</if>
<if test="sex != null">
and sex = #{sex}
</if>
</where>
</select>

然后执行测试代码中的 testFindByCondition 方法, 截取的部分结果如下(注意结果中的 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
2020-08-23 22:46:21,752 207    [           main] DEBUG .apache.ibatis.io.ResolverUtil  - Checking to see if class club.guoshizhan.dao.IUserDao matches criteria [is assignable to Object]
2020-08-23 22:46:21,752 207 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.UserDaoImpl matches criteria [is assignable to Object]
2020-08-23 22:46:22,008 463 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-23 22:46:22,254 709 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c8530f]
2020-08-23 22:46:22,262 717 [ main] DEBUG n.dao.IUserDao.findByCondition - ==> Preparing: select * from user /* 此处使用了 where 标签 */ WHERE username like ? and sex = ?
2020-08-23 22:46:22,300 755 [ main] DEBUG n.dao.IUserDao.findByCondition - ==> Parameters: %王%(String), 男(String)
2020-08-23 22:46:22,330 785 [ main] DEBUG n.dao.IUserDao.findByCondition - <== Total: 2
2020-08-23 22:46:22,331 786 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c8530f]
2020-08-23 22:46:22,331 786 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c8530f]
User{id=43, username='大王', birthday=Tue Nov 12 10:19:13 CST 2019, sex='男', address='Los Angeles'}
User{id=46, username='老王', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='北京'}

foreach 标签

现在提出一个需求:传入多个 id 查询用户信息。 我们如何做呢?当然,我们可以用下边两个 SQL 实现:

1
2
SELECT * FROM USERS WHERE username LIKE '%王%' AND (id =10 OR id =89 OR id=16)
SELECT * FROM USERS WHERE username LIKE '%王%' AND id IN (10,89,16)

根据上面的需求,问题来了:我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。 这样的话,mybatis 将如何进行参数的传递? 接着往下看吧!!!


第一步: 编写 QueryVo 类,新加了 ids 属性 代码如下:

QueryVo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package club.guoshizhan.domain;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/8/22 20:34
* @Description: QueryVo 实体类(此类中还包含了 User 实体类)
*/
public class QueryVo {

private User user;
private List<Integer> ids;

public List<Integer> getIds() {
return ids;
}

public void setIds(List<Integer> ids) {
this.ids = ids;
}

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

}

第二步: 编写 IUserDao 类,新加了 findUserInIds 方法。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package club.guoshizhan.dao;

import club.guoshizhan.domain.QueryVo;
import club.guoshizhan.domain.User;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
List<User> findAll();

// 保存用户
void saveUser(User user);

// 修改用户
void updateUser(User user);

// 根据 id 删除用户
void deleteUser(Integer id);

// 根据 id 查询用户
User findById(Integer userId);

// 模糊查询用户
List<User> findByName(String username);

// 根据传入的条件进行查询。可能有用户名,可能有性别,也可能什么都有,也可能什么都没有
List<User> findByCondition(User user);

// 根据 QueryVo 中提供的 id 集合查询用户信息
List<User> findUserInIds(QueryVo vo);

}

第三步: 编写 IUserDaoImpl 类,重写 findUserInIds 方法。 代码如下:

IUserDaoImpl.java
1
2
3
4
5
6
7
8
9
@Override
public List<User> findUserInIds(QueryVo vo) {
// 01-根据 factory 获取 SqlSession 对象
SqlSession session = factory.openSession();
List<User> list = session.selectList("club.guoshizhan.dao.IUserDao.findByCondition", vo);
// 03-释放资源
session.close();
return list;
}

第四步: 编写 IUserDao.xml 配置文件,新加如下代码(里面就是 foreach 标签 的用法)。 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 根据 QueryVo 中提供的 id 集合查询用户信息 -->
<select id="findUserInIds" parameterType="club.guoshizhan.domain.QueryVo" resultType="club.guoshizhan.domain.User">
select * from user
/* 此处使用了 where 标签 */
<where>
<if test="ids != null and ids.size() > 0">
/* 此处使用了 foreach 标签。如果把此行注释去掉,那么下方 open 的值是 ="and id in(" */
<foreach collection="ids" open="id in(" close=")" item="aaaId" separator=",">
#{aaaId}
</foreach>
</if>
</where>
</select>

foreach 标签 的相关属性的详细介绍如下:

1
2
3
4
5
6
7
8
9
10
11
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+ SQL语句:select 字段 from user where id in (?)
+++ <foreach> 标签用于遍历集合,它的属性有:
+ collection: 代表要遍历的集合元素,注意编写时不要写#{}
+ open: 代表语句的开始部分
+ close: 代表结束部分
+ item: 代表遍历集合的每个元素,生成的变量名
+ sperator: 代表分隔符

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

第五步: 编写测试类代码,然后执行相应的方法即可。 代码如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 根据传入的条件进行查询
@Test
public void testFindUserInIds() {
QueryVo vo = new QueryVo();
List<Integer> list = new ArrayList<>();
list.add(42);
list.add(43);
list.add(46);
vo.setIds(list);

List<User> users = userDao.findUserInIds(vo);
for (User user1 : users) {
System.out.println(user1);
}
}

执行测试代码中的 testFindUserInIds 方法, 截取的部分结果如下(注意结果中的 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
2020-08-24 00:10:27,079 145    [           main] DEBUG .apache.ibatis.io.ResolverUtil  - Checking to see if class club.guoshizhan.dao.UserDaoImpl matches criteria [is assignable to Object]
2020-08-24 00:10:27,244 310 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-24 00:10:27,552 618 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@76c8cd]
2020-08-24 00:10:27,559 625 [ main] DEBUG han.dao.IUserDao.findUserInIds - ==> Preparing: select * from user /* 此处使用了 where 标签 */ WHERE /* 此处使用了 foreach 标签。如果把此行注释去掉,那么下方 open 的值是 ="and id in(" */ id in( ? , ? , ? )
2020-08-24 00:10:27,589 655 [ main] DEBUG han.dao.IUserDao.findUserInIds - ==> Parameters: 42(Integer), 43(Integer), 46(Integer)
2020-08-24 00:10:27,609 675 [ main] DEBUG han.dao.IUserDao.findUserInIds - <== Total: 3
2020-08-24 00:10:27,610 676 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@76c8cd]
2020-08-24 00:10:27,610 676 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@76c8cd]
User{id=42, username='小二王', birthday=Fri Mar 02 15:09:37 CST 2018, sex='女', address='北京金燕龙'}
User{id=43, username='大王', birthday=Tue Nov 12 10:19:13 CST 2019, sex='男', address='Los Angeles'}
User{id=46, username='老王', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='北京'}

TIPS: mybatis 的动态 SQL 到此结束!!!

mybatis 的多表操作

既然需要实现多表操作,那么就必须知道表与表之间的关系。 具体介绍如下:

1
2
3
4
5
6
7
8
9
10
11
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ 表与表之间的关系:
+ 一对多 举例:用户和订单就是一对多,一个用户可以下多个订单
+ 多对一 举例:订单和用户就是多对一,多个订单可以被一个用户所支付
+ 一对一 举例:一个人只能有一个身份证号,一个身份证号只能属于一个人
+ 多对多 举例:一个学生可以选择多门课程,一门课程可以被多个学生选择

+++ 特例:如果拿出每一个订单,他都只能属于一个用户。所以 Mybatis 就把多对一看成了一对一。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

前期准备

要实现 多表操作 ,必须要多张表呀。所以需要先完成准备工作。 如下:

1
2
3
4
5
6
7
8
9
10
11
12
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ mybatis 中的多表查询:用户表和账户表
+ 一个用户可以有多个账户,一个账户只能属于一个用户(多个账户也可以属于同一个用户)

+++ 要实现两张表的关联操作,需要如下步骤:
+ 1、建立两张表:用户表,账户表。让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加
+ 2、建立两个实体类:用户实体类和账户实体类。让用户和账户的实体类能体现出来一对多的关系
+ 3、建立两个配置文件。用户的配置文件和账户的配置文件
+ 4、实现配置:当我们查询用户时,可以同时得到用户下所包含的账户信息;当我们查询账户时,可以同时得到账户的所属用户信息

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

建表语句mybatis 的环境搭建 小结已经完成了,所以第一步就搞定了。我们的用户实体类(User 类)在先前已经创建了,所以第二步操作只需要在 domain 包下建立账户实体类(Account 类),然后在 dao 包下新建对应的 dao 接口。 相关代码如下:

Account.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package club.guoshizhan.domain;

import java.io.Serializable;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 10:59
* @Description: Account 类
*/
public class Account implements Serializable {

private Integer id;
private Integer uid;
private Double money;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Integer getUid() {
return uid;
}

public void setUid(Integer uid) {
this.uid = uid;
}

public Double getMoney() {
return money;
}

public void setMoney(Double money) {
this.money = money;
}

@Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
'}';
}

}

账户实体类完成之后,我们去 dao 包下新建 IAccountDao 接口。 代码如下:

IAccountDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package club.guoshizhan.dao;

import club.guoshizhan.domain.Account;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 11:07
* @Description: Account 的 dao 接口
*/
public interface IAccountDao {

// 查询所有账户
List<Account> findAll();

}

接口搞定之后,我们去 resources 目录下的 club/guoshizhan/dao 下新建 IAccountDao.xml 配置文件。 代码如下:

IAccountDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<?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">

<!-- 01-持久层映射配置中,mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名 -->
<mapper namespace="club.guoshizhan.dao.IAccountDao">

<select id="findAll" resultType="Account">
select * from account;
</select>

</mapper>

最后,我们编写测试类进行测试。 代码如下:

AccountTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package club.guoshizhan.Test;

import club.guoshizhan.dao.IAccountDao;
import club.guoshizhan.domain.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class AccountTest {

private InputStream in;
private IAccountDao accountDao;
private SqlSession session;

// 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {

// 01-读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 02-创建 SqlSessionFactory 工厂 ,SqlSessionFactory 是一个接口,不能 new 对象(因此需要使用它的实现类 SqlSessionFactoryBuilder)
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 03-使用工厂对象创建 dao 对象
session = factory.openSession();
accountDao = session.getMapper(IAccountDao.class);

}

// 查询所有账户
@Test
public void testFindAll() {
// 05-使用代理对象执行方法
List<Account> accounts = accountDao.findAll();
for (Account account : accounts) {
System.out.println(account);
}
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destory() throws IOException {
// 06-关闭资源
in.close();
}

}

执行上述的 testFindAll 方法, 结果如下(注意 SQL 语句)

1
2
3
4
5
6
7
8
9
10
2020-08-24 13:42:32,968 261    [           main] DEBUG .apache.ibatis.io.ResolverUtil  - Checking to see if class club.guoshizhan.dao.IAccountDao matches criteria [is assignable to Object]
2020-08-24 13:42:32,968 261 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.IUserDao matches criteria [is assignable to Object]
2020-08-24 13:42:32,968 261 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.UserDaoImpl matches criteria [is assignable to Object]
2020-08-24 13:42:33,189 482 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-24 13:42:33,454 747 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@121c9d6]
2020-08-24 13:42:33,462 755 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Preparing: select * from account;
2020-08-24 13:42:33,492 785 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Parameters:
2020-08-24 13:42:33,509 802 [ main] DEBUG hizhan.dao.IAccountDao.findAll - <== Total: 2
Account{id=1, uid=46, money=1000.0}
Account{id=3, uid=46, money=2000.0}

TIPS: 前期准备工作 到此结束!!!

一对一

那么现在就搞定 一对一 的表结构。第一步: domain 包下新建 AccountUser 类。 代码如下:

AccountUser.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package club.guoshizhan.domain;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 13:57
* @Description: AccountUser 类
*/
public class AccountUser extends Account {

private String username;
private String address;

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

@Override
public String toString() {
return super.toString().substring(0, super.toString().length() - 1) + ", " +
"username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}

}

第二步: IAccountDao 接口中新加 findAllAccount 方法。 代码如下:

IAccountDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package club.guoshizhan.dao;

import club.guoshizhan.domain.Account;
import club.guoshizhan.domain.AccountUser;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 11:07
* @Description: Account 的 dao 接口
*/
public interface IAccountDao {

// 查询所有账户
List<Account> findAll();

// 查询所有账户,并且带有用户名称和地址信息
List<AccountUser> findAllAccount();

}

第三步: IAccountDao.xml 文件中新加如下代码:

IAccountDao.xml
1
2
3
4
<!-- 查询所有账户,并且带有用户名称和地址信息 -->
<select id="findAllAccount" resultType="AccountUser">
SELECT a.*,u.username,u.address FROM USER u , account a WHERE a.uid = u.id
</select>

第四步: 编写测试方法进行测试。 代码如下:

AccountTest.java
1
2
3
4
5
6
7
8
9
// 查询所有账户,并且带有用户名称和地址信息
@Test
public void testFindAllAccount() {
// 05-使用代理对象执行方法
List<AccountUser> accountUsers = accountDao.findAllAccount();
for (AccountUser account : accountUsers) {
System.out.println(account);
}
}

执行上述的 testFindAllAccount 方法, 结果如下(注意 SQL 语句)

1
2
3
4
5
6
7
8
9
10
2020-08-24 14:11:00,871 252    [           main] DEBUG .apache.ibatis.io.ResolverUtil  - Checking to see if class club.guoshizhan.dao.IAccountDao matches criteria [is assignable to Object]
2020-08-24 14:11:00,871 252 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.IUserDao matches criteria [is assignable to Object]
2020-08-24 14:11:00,872 253 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.UserDaoImpl matches criteria [is assignable to Object]
2020-08-24 14:11:01,116 497 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-24 14:11:01,423 804 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7f383f]
2020-08-24 14:11:01,430 811 [ main] DEBUG dao.IAccountDao.findAllAccount - ==> Preparing: SELECT a.*,u.username,u.address FROM USER u , account a WHERE a.uid = u.id
2020-08-24 14:11:01,460 841 [ main] DEBUG dao.IAccountDao.findAllAccount - ==> Parameters:
2020-08-24 14:11:01,480 861 [ main] DEBUG dao.IAccountDao.findAllAccount - <== Total: 2
Account{id=1, uid=46, money=1000.0, username='老王', address='北京'}
Account{id=3, uid=46, money=2000.0, username='老王', address='北京'}

虽然结果是查询出来了,但是,表结构之间的关系却没有体现出来。 所以,接下来需要解决的问题就是 让实体类能够体现出表结构之间的关系。 那么如何做呢?接着往下看(注意:以下是 一对一 体现)!!!


第一步: Account 实体类添加一个属性,从而体现表结构之间的关系。 代码如下:

Account.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package club.guoshizhan.domain;

import java.io.Serializable;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 10:59
* @Description: Account 类
*/
public class Account implements Serializable {

private Integer id;
private Integer uid;
private Double money;

// 从表实体 Account 应该包含一个主表实体 User 的引用
private User user;

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Integer getUid() {
return uid;
}

public void setUid(Integer uid) {
this.uid = uid;
}

public Double getMoney() {
return money;
}

public void setMoney(Double money) {
this.money = money;
}

@Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
'}';
}

}

第二步: 修改 IAccountDao.xml 文件重点在 查询所有账户 那里。 代码如下:

IAccountDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?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">

<!-- 01-持久层映射配置中,mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名 -->
<mapper namespace="club.guoshizhan.dao.IAccountDao">

<!-- 定义封装 Account 和 User 的 resultMap -->
<resultMap id="accountMap" type="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 上述封装只能保证 Account 实体类的数据封装完成,并不能把 User 实体类的数据进行封装 -->
<!-- 一对一的关系映射,配置封装 User 的内容 -->
<association property="user" column="uid" javaType="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
</association>
</resultMap>

<!-- 查询所有账户 -->
<select id="findAll" resultMap="accountMap">
SELECT u.*,a.id AS aid,a.uid,a.money FROM USER u,account a WHERE u.id = a.uid
</select>

<!-- 查询所有账户,并且带有用户名称和地址信息 -->
<select id="findAllAccount" resultType="AccountUser">
SELECT a.*,u.username,u.address FROM USER u , account a WHERE a.uid = u.id
</select>

</mapper>

第三步: 修改测试类 AccountTest 中的 testFindAll 方法。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
// 查询所有用户
@Test
public void testFindAll() {
// 05-使用代理对象执行方法
List<Account> accounts = accountDao.findAll();
for (Account account : accounts) {
System.out.println("======== 一对一表结构结果输出 ========");
System.out.println(account);
System.out.println(account.getUser());
}
}

第四步: 执行上述的 testFindAll 方法, 结果如下(注意 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2020-08-24 15:00:58,839 154    [           main] DEBUG .apache.ibatis.io.ResolverUtil  - Checking to see if class club.guoshizhan.dao.IAccountDao matches criteria [is assignable to Object]
2020-08-24 15:00:58,839 154 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.IUserDao matches criteria [is assignable to Object]
2020-08-24 15:00:58,840 155 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.UserDaoImpl matches criteria [is assignable to Object]
2020-08-24 15:00:58,974 289 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-24 15:00:59,260 575 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@126f7b2]
2020-08-24 15:00:59,266 581 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Preparing: SELECT u.*,a.id AS aid,a.uid,a.money FROM USER u,account a WHERE u.id = a.uid
2020-08-24 15:00:59,292 607 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Parameters:
2020-08-24 15:00:59,312 627 [ main] DEBUG hizhan.dao.IAccountDao.findAll - <== Total: 2
======== 一对一表结构结果输出 ========
Account{id=1, uid=46, money=1000.0}
User{id=46, username='老王', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='北京'}
======== 一对一表结构结果输出 ========
Account{id=3, uid=46, money=2000.0}
User{id=46, username='老王', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='北京'}

TIPS: 一对一 表结构到此结束!!!

一对多

话不多说,现在就解决 一对多 的表结构。第一步:User 实体类添加一个属性,从而体现一对多的表结构。 代码如下:

User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package club.guoshizhan.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:52
* @Description: User 实体类
*/
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;

// 一对多关系映射,主表实体应该包含从表实体的集合引用
private List<Account> accounts;

public List<Account> getAccounts() {
return accounts;
}

public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}
}

第二步: 修改 IUserDao.xml 文件,把以下代码替换原来的 id="findAll" 的那部分查询代码 。 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 定义User 的 resultMap -->
<resultMap id="userMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<!-- 配置 user 对象中 accounts 的集合映射。ofType 代表集合中元素的类型 -->
<collection property="accounts" ofType="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>

<select id="findAll" resultMap="userMap">
SELECT u.*,a.id as aid,a.money,a.uid FROM USER u LEFT OUTER JOIN account a ON a.uid=u.id
</select>

第三步: 编写测试类 UserTest 进行测试。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class UserTest {

private InputStream in;
private IUserDao userDao;
private SqlSession session;

// 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
}

// 查询所有用户
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println("======== 一对多表结构结果输出 ========");
System.out.println(user);
System.out.println(user.getAccounts());
}
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destory() throws IOException {
// 06-关闭资源
in.close();
}

}

第四步: 执行 testFindAll 方法。 测试结果如下(注意 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2020-08-24 15:46:09,919 184    [           main] DEBUG .apache.ibatis.io.ResolverUtil  - Checking to see if class club.guoshizhan.dao.IAccountDao matches criteria [is assignable to Object]
2020-08-24 15:46:09,919 184 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.IUserDao matches criteria [is assignable to Object]
2020-08-24 15:46:09,919 184 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.UserDaoImpl matches criteria [is assignable to Object]
2020-08-24 15:46:10,087 352 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-24 15:46:10,394 659 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@126f7b2]
2020-08-24 15:46:10,400 665 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - ==> Preparing: SELECT u.*,a.id as aid,a.money,a.uid FROM USER u LEFT OUTER JOIN account a ON a.uid=u.id
2020-08-24 15:46:10,435 700 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - ==> Parameters:
2020-08-24 15:46:10,465 730 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - <== Total: 8
======== 一对多表结构结果输出 ========
User{id=42, username='小二王', birthday=Fri Mar 02 15:09:37 CST 2018, sex='女', address='北京金燕龙'}
[]
======== 一对多表结构结果输出 ========
User{id=43, username='大王', birthday=Tue Nov 12 10:19:13 CST 2019, sex='男', address='Los Angeles'}
[]
======== 一对多表结构结果输出 ========
User{id=46, username='老王', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='北京'}
[Account{id=1, uid=46, money=1000.0}, Account{id=3, uid=46, money=2000.0}]
======== 一对多表结构结果输出 ========
User{id=49, username='Jack', birthday=Mon Oct 28 09:15:39 CST 2019, sex='男', address='上海闵行'}
[]
======== 一对多表结构结果输出 ========
User{id=51, username='Lisa', birthday=Mon Oct 28 09:17:31 CST 2019, sex='女', address='广东佛山'}
[]
======== 一对多表结构结果输出 ========
User{id=52, username='Tom', birthday=Mon Oct 28 10:26:42 CST 2019, sex='男', address='广东佛山'}
[]
======== 一对多表结构结果输出 ========
User{id=54, username='Bob', birthday=Tue Nov 12 10:21:41 CST 2019, sex='男', address='广东佛山'}
[]

TIPS: 一对多 表结构到此结束!!!

多对多

为了实现 多对多 操作,我们需要新建一个工程(主要是为了操作方便,排除一些不必要的干扰)。 也可点击右侧下载工程: 多对多工程。下载工程之后,我们来看一下需要做的准备工作。如下:

1
2
3
4
5
6
7
8
9
10
11
12
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ mybatis 中的多表查询之多对多:用户表和角色表
+ 一个用户可以有多个角色,一个角色也可以赋予多个用户

+++ 要实现两张表的关联操作,需要如下步骤:
+ 1、建立两张表:用户表,角色表。让用户表和角色表具有多对多的关系。需要使用中间表,中间表中包含各自的主键,在中间表中是外键。
+ 2、建立两个实体类:用户实体类和角色实体类。让用户和角色的实体类能体现出来多对多的关系(各自包含对方一个集合引用)
+ 3、建立两个配置文件。用户的配置文件和角色的配置文件
+ 4、实现配置:当我们查询用户时,可以同时得到用户所包含的角色信息;当我们查询角色时,可以同时得到角色的所赋予的用户信息

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

建表语句mybatis 的环境搭建 小结已经完成了,所以第一步就搞定了。我们的用户实体类(User 类)在工程中已经创好了,所以第二步操作只需要在 domain 包下建立角色实体类(Role 类),然后在 dao 包下新建对应的 dao 接口。 相关代码如下:

Role.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package club.guoshizhan.domain;

import java.io.Serializable;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 16:30
* @Description: 角色类
*/

public class Role implements Serializable {

private Integer roleId;
private String roleName;
private String roleDesc;

// 多对多的关系映射,表示一个角色可以赋予多个用户
private List<User> users;

public List<User> getUsers() {
return users;
}

public void setUsers(List<User> users) {
this.users = users;
}

public Integer getRoleId() {
return roleId;
}

public void setRoleId(Integer roleId) {
this.roleId = roleId;
}

public String getRoleName() {
return roleName;
}

public void setRoleName(String roleName) {
this.roleName = roleName;
}

public String getRoleDesc() {
return roleDesc;
}

public void setRoleDesc(String roleDesc) {
this.roleDesc = roleDesc;
}

@Override
public String toString() {
return "Role{" +
"roleId=" + roleId +
", roleName='" + roleName + '\'' +
", roleDesc='" + roleDesc + '\'' +
'}';
}

}

角色实体类完成之后,我们去 dao 包下新建 IRoleDao 接口。 代码如下:

IRoleDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package club.guoshizhan.dao;

import club.guoshizhan.domain.Role;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 16:32
* @Description: IRoleDao 接口
*/
public interface IRoleDao {

// 查询所有角色
List<Role> findAll();

}

接口搞定之后,我们去 resources 目录下的 club/guoshizhan/dao 下新建 IRoleDao.xml 配置文件。 代码如下:

IRoleDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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">
<mapper namespace="club.guoshizhan.dao.IRoleDao">

<!-- 定义角色表的 resultMap -->
<resultMap id="roleMap" type="club.guoshizhan.domain.Role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>

<collection property="users" ofType="club.guoshizhan.domain.User">
<id column="id" property="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
</collection>
</resultMap>

<!-- 查询所有 -->
<select id="findAll" resultMap="roleMap">
SELECT u.*,r.id AS rid,r.role_name,r.role_desc FROM role r
LEFT OUTER JOIN user_role ur ON r.id=ur.rid
LEFT OUTER JOIN USER u ON u.id=ur.uid
</select>

</mapper>

最后,我们新建 RoleTest 测试类进行测试。 代码如下:

RoleTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package club.guoshizhan.test;

import club.guoshizhan.dao.IRoleDao;
import club.guoshizhan.domain.Role;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

public class RoleTest {

private InputStream in;
private SqlSession session;
private IRoleDao roleDao;

@Before
public void init()throws Exception{
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
session = factory.openSession();
roleDao = session.getMapper(IRoleDao.class);
}

@Test
public void testFindAll(){
List<Role> roles = roleDao.findAll();
for(Role role : roles){
System.out.println("-------- 每个角色的信息 --------");
System.out.println(role);
System.out.println(role.getUsers());
}
}

@After
public void destory()throws Exception{
session.commit();
session.close();
in.close();
}

}

执行上述的 testFindAll 方法, 结果如下(注意 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2020-08-24 17:13:04,762 187    [           main] DEBUG .apache.ibatis.io.ResolverUtil  - Checking to see if class club.guoshizhan.dao.IRoleDao matches criteria [is assignable to Object]
2020-08-24 17:13:04,762 187 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.IUserDao matches criteria [is assignable to Object]
2020-08-24 17:13:04,964 389 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-24 17:13:05,244 669 [ main] DEBUG source.pooled.PooledDataSource - Created connection 8864840.
2020-08-24 17:13:05,245 670 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@874448]
2020-08-24 17:13:05,247 672 [ main] DEBUG uoshizhan.dao.IRoleDao.findAll - ==> Preparing: SELECT u.*,r.id AS rid,r.role_name,r.role_desc FROM role r LEFT OUTER JOIN user_role ur ON r.id=ur.rid LEFT OUTER JOIN USER u ON u.id=ur.uid
2020-08-24 17:13:05,275 700 [ main] DEBUG uoshizhan.dao.IRoleDao.findAll - ==> Parameters:
2020-08-24 17:13:05,302 727 [ main] DEBUG uoshizhan.dao.IRoleDao.findAll - <== Total: 5
-------- 每个角色的信息 --------
Role{roleId=1, roleName='院长', roleDesc='管理整个学院'}
[User{id=49, username='Jack', birthday=Mon Oct 28 09:15:39 CST 2019, sex='男', address='上海闵行'}, User{id=51, username='Lisa', birthday=Mon Oct 28 09:17:31 CST 2019, sex='女', address='广东佛山'}]
-------- 每个角色的信息 --------
Role{roleId=2, roleName='总裁', roleDesc='管理整个公司'}
[User{id=51, username='Lisa', birthday=Mon Oct 28 09:17:31 CST 2019, sex='女', address='广东佛山'}]
-------- 每个角色的信息 --------
Role{roleId=3, roleName='校长', roleDesc='管理整个学校'}
[User{id=49, username='Jack', birthday=Mon Oct 28 09:15:39 CST 2019, sex='男', address='上海闵行'}, User{id=51, username='Lisa', birthday=Mon Oct 28 09:17:31 CST 2019, sex='女', address='广东佛山'}]

上述的操作实现了 从角色到用户的多对多操作。那么接下来要实现 从用户到角色的多对多操作 。这个过程和上面的操作极其相似, 所以下面使用简略步骤进行描述。


第一步: User 类加上 roles 属性,用于多对多的关系映射。 代码如下:

User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package club.guoshizhan.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:52
* @Description: User 实体类
*/
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;

// 多对多的关系映射,表示一个用户可以具备多个角色
private List<Role> roles;

public List<Role> getRoles() {
return roles;
}

public void setRoles(List<Role> roles) {
this.roles = roles;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}
}

第二步: 编写 IUserDao.xml 配置文件。 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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">

<mapper namespace="club.guoshizhan.dao.IUserDao">

<resultMap id="userMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>

<!-- 配置角色映射集合 -->
<collection property="roles" ofType="role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
</collection>
</resultMap>

<select id="findAll" resultMap="userMap">
SELECT u.*,r.id AS rid,r.role_name,r.role_desc FROM USER u
LEFT OUTER JOIN user_role ur ON u.id=ur.uid
LEFT OUTER JOIN role r ON r.id=ur.rid
</select>

</mapper>

最后,在 UserTest 测试类中编写方法进行测试。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package club.guoshizhan.test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class UserTest {

private InputStream in;
private IUserDao userDao;
private SqlSession session;

// 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
}

// 查询所有用户
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println("-------- 每个用户的信息 --------");
System.out.println(user);
System.out.println(user.getRoles());
}
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destory() throws IOException {
// 06-关闭资源
in.close();
}

}

执行上述的 testFindAll 方法, 结果如下(注意 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2020-08-24 19:26:55,933 177    [           main] DEBUG .apache.ibatis.io.ResolverUtil  - Checking to see if class club.guoshizhan.dao.IRoleDao matches criteria [is assignable to Object]
2020-08-24 19:26:55,934 178 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.IUserDao matches criteria [is assignable to Object]
2020-08-24 19:26:56,080 324 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-24 19:26:56,422 666 [ main] DEBUG source.pooled.PooledDataSource - Created connection 8864840.
2020-08-24 19:26:56,423 667 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@874448]
2020-08-24 19:26:56,425 669 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - ==> Preparing: SELECT u.*,r.id AS rid,r.role_name,r.role_desc FROM USER u LEFT OUTER JOIN user_role ur ON u.id=ur.uid LEFT OUTER JOIN role r ON r.id=ur.rid
2020-08-24 19:26:56,449 693 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - ==> Parameters:
2020-08-24 19:26:56,474 718 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - <== Total: 10
-------- 每个用户的信息 --------
User{id=42, username='小二王', birthday=Fri Mar 02 15:09:37 CST 2018, sex='女', address='北京金燕龙'}
[]
-------- 每个用户的信息 --------
User{id=43, username='大王', birthday=Tue Nov 12 10:19:13 CST 2019, sex='男', address='Los Angeles'}
[]
-------- 每个用户的信息 --------
User{id=46, username='老王', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='北京'}
[]
-------- 每个用户的信息 --------
User{id=49, username='Jack', birthday=Mon Oct 28 09:15:39 CST 2019, sex='男', address='上海闵行'}
[Role{roleId=1, roleName='院长', roleDesc='管理整个学院'}, Role{roleId=3, roleName='校长', roleDesc='管理整个学校'}]
-------- 每个用户的信息 --------
User{id=51, username='Lisa', birthday=Mon Oct 28 09:17:31 CST 2019, sex='女', address='广东佛山'}
[Role{roleId=1, roleName='院长', roleDesc='管理整个学院'}, Role{roleId=2, roleName='总裁', roleDesc='管理整个公司'}, Role{roleId=3, roleName='校长', roleDesc='管理整个学校'}]
-------- 每个用户的信息 --------
User{id=52, username='Tom', birthday=Mon Oct 28 10:26:42 CST 2019, sex='男', address='广东佛山'}
[]
-------- 每个用户的信息 --------
User{id=54, username='Bob', birthday=Tue Nov 12 10:21:41 CST 2019, sex='男', address='广东佛山'}
[]

TIPS: 多对多 表结构到此结束!!!

JNDI 知识

这个部分的内容不是很重要,所以就先跳过,以后有空再完善。

1
2
3
4
5
6
7
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+ 这个部分的内容不是很重要,所以就先跳过,以后有空再完善
+ 这个部分的内容不是很重要,所以就先跳过,以后有空再完善
+ 这个部分的内容不是很重要,所以就先跳过,以后有空再完善

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

TIPS: JNDI 知识 到此结束!!!

延迟加载和缓存

mybatis 的延迟加载

有没有想过,为什么需要延迟加载技术?延迟加载主要用来干嘛的? 带着问题往下看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ 问题:在一对多中,当我们有一个用户,它有100个账户。
+ 1、在查询用户的时候,要不要把关联的账户查出来? 2、在查询账户的时候,要不要把关联的用户查出来?

+++ 什么是延迟加载?
+ 在真正使用数据时才发起查询,不用的时候不查询。这种加载方式称之为延迟加载,也叫做按需加载(懒加载)
+ 延迟加载举例:在查询用户时,用户下的账户信息应该是这样的:什么时候使用,就什么时候查询,而不是一查询用户就把所有账户显示出来

+++ 什么是立即加载?
+ 不管用不用,只要一调用方法,马上发起查询。这中加载方式就叫做立即加载
+ 立即加载举例:在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来,否则我哪知道这个账户是谁的。

+++ 在对应的四种表关系(一对多,多对一,一对一,多对多)中,它们分别适合哪种加载方式?
+ 一对多,多对多:通常情况下我们都是采用延迟加载。 多对一,一对一:通常情况下我们都是采用立即加载。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

了解完 延迟加载 相关概念之后,我们就来操作一下吧!(一对一的延迟加载) 第一步: 新建一个 maven 工程(自己构建一下,包括新建包结构等等,这里不过多介绍了),然后加入如下依赖:

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>

<dependencies>

<!-- mybatis 依赖,必须要 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>

<!-- mysql 数据库驱动依赖,必须要 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>

<!-- 日志依赖,不是一定要 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>

<!-- 测试依赖,不是一定要 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>

</dependencies>

第二步: domain 包下新建一个 User 实体类和 Account 实体类。 代码如下:

User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package club.guoshizhan.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:52
* @Description: User 实体类
*/
public class User implements Serializable {

private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;

private List<Account> accounts;

public List<Account> getAccounts() {
return accounts;
}

public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

}
Account.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package club.guoshizhan.domain;

import java.io.Serializable;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 10:59
* @Description: Account 类
*/
public class Account implements Serializable {

private Integer id;
private Integer uid;
private Double money;

private User user;

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Integer getUid() {
return uid;
}

public void setUid(Integer uid) {
this.uid = uid;
}

public Double getMoney() {
return money;
}

public void setMoney(Double money) {
this.money = money;
}

@Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
'}';
}

}

第三步: dao 包下新建 IUserDao 接口 和 IAccountDao 接口。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 查询所有用户
List<User> findAll();

// 根据 id 查询用户
User findById(Integer userId);

}
IAccountDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package club.guoshizhan.dao;

import club.guoshizhan.domain.Account;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 11:07
* @Description: Account 的 dao 接口
*/
public interface IAccountDao {

// 查询所有账户,同时还需要获取到当前账户的所属用户信息
List<Account> findAll();

}

第四步: resources 目录下新建三个配置文件。 代码如下:

jdbcConfig.properties
1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy_mybatis
jdbc.username=root
jdbc.password=root
SqlMapConfig.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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">

<!-- 01-mybatis 的主配置文件 -->
<configuration>
<properties resource="jdbcConfig.properties"></properties>

<typeAliases>
<package name="club.guoshizhan.domain"></package>
</typeAliases>

<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

<mappers>
<package name="club.guoshizhan.dao"></package>
</mappers>
</configuration>
log4j.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE

# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

第五步: resources 目录的包结构下新建 IUserDao.xml 文件 和 IAccountDao.xml 文件。 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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">

<mapper namespace="club.guoshizhan.dao.IUserDao">

<!-- 定义User 的 resultMap -->
<resultMap id="userMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>

<!-- 配置 user 对象中 accounts 的集合映射。ofType 代表集合中元素的类型 -->
<collection property="accounts" ofType="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>

<select id="findAll" resultMap="userMap">
SELECT u.*,a.id as aid,a.money,a.uid FROM USER u LEFT OUTER JOIN account a ON a.uid=u.id
</select>

<!-- 根据 id 查询用户 -->
<select id="findById" parameterType="Integer" resultType="User">
select * from user where id=#{id}
</select>

</mapper>
IAccountDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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">

<mapper namespace="club.guoshizhan.dao.IAccountDao">

<resultMap id="accountMap" type="account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</resultMap>

<!-- 查询所有账户 -->
<select id="findAll" resultMap="accountMap">
SELECT * FROM account
</select>

</mapper>

第六步: 编写 AccountTest 测试类。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package club.guoshizhan.test;

import club.guoshizhan.dao.IAccountDao;
import club.guoshizhan.domain.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class AccountTest {

private InputStream in;
private IAccountDao accountDao;
private SqlSession session;

@Before
public void init() throws Exception {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
accountDao = session.getMapper(IAccountDao.class);

}

// 查询所有用户
@Test
public void testFindAll() {
List<Account> accounts = accountDao.findAll();
for (Account account : accounts) {
System.out.println("======== 一对一 延迟加载 结果输出 ========");
System.out.println(account);
System.out.println(account.getUser());
}
}


@After
public void destory() throws IOException {
session.close();
in.close();
}

}

执行上述 testFindAll 方法, 结果如下(注意 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2020-08-27 16:33:33,600 658    [           main] DEBUG .apache.ibatis.io.ResolverUtil  - Checking to see if class club.guoshizhan.dao.IAccountDao matches criteria [is assignable to Object]
2020-08-27 16:33:33,600 658 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.IUserDao matches criteria [is assignable to Object]
2020-08-27 16:33:33,886 944 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-27 16:33:34,309 1367 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]
2020-08-27 16:33:34,316 1374 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Preparing: SELECT * FROM account
2020-08-27 16:33:34,348 1406 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Parameters:
2020-08-27 16:33:34,368 1426 [ main] DEBUG hizhan.dao.IAccountDao.findAll - <== Total: 2
======== 一对一 延迟加载 结果输出 ========
Account{id=1, uid=46, money=1000.0}
null
======== 一对一 延迟加载 结果输出 ========
Account{id=3, uid=46, money=2000.0}
null
2020-08-27 16:33:34,369 1427 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]
2020-08-27 16:33:34,369 1427 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]

为什么上述没有查到用户的信息呢(结果是 null)?因为我们只查询了 account 表,所以只有账户信息,没有用户信息。所以此时就需要去配置 延迟加载 了,接着往下看!!!


那么如何配置延迟加载呢? 第一步: IAccountDao.xml 文件中加入如下代码:

IAccountDao.xml
1
2
3
4
5
6
7
8
<resultMap id="accountMap" type="account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
+ <!-- 配置一对一延迟加载,使用 select 属性指定内容,就是去执行 IUserDao 下的 findById 方法 -->
+ <association property="user" column="uid" javaType="user" select="club.guoshizhan.dao.IUserDao.findById">
+ </association>
</resultMap>

第二步: SqlMapConfig.xml 文件中加入如下代码:

SqlMapConfig.xml
1
2
3
4
5
6
7
8
9
<configuration>
<properties resource="jdbcConfig.properties"></properties>

+ <!-- 配置延迟加载,各项参数在官网写的特别详细,建议参照官网学习 -->
+ <settings>
+ <!-- 开启 mybatis 支持延迟加载 -->
+ <setting name="lazyLoadingEnabled" value="true"/>
+ <setting name="aggressiveLazyLoading" value="false"/>
+ </settings>

上述两步做完之后,我们去执行测试类中的 testFindAll 方法。(为了演示明显,我去数据库加了两条数据) 结果如下(注意 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2020-08-27 16:51:03,382 516    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-27 16:51:03,696 830 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]
2020-08-27 16:51:03,708 842 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Preparing: SELECT * FROM account
2020-08-27 16:51:03,738 872 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Parameters:
2020-08-27 16:51:03,808 942 [ main] DEBUG hizhan.dao.IAccountDao.findAll - <== Total: 4
======== 一对一 延迟加载 结果输出 ========
2020-08-27 16:51:03,809 943 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-27 16:51:03,809 943 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 46(Integer)
2020-08-27 16:51:03,814 948 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
Account{id=1, uid=46, money=1000.0}
User{id=46, username='老王', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='北京'}
======== 一对一 延迟加载 结果输出 ========
Account{id=3, uid=46, money=2000.0}
User{id=46, username='老王', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='北京'}
======== 一对一 延迟加载 结果输出 ========
2020-08-27 16:51:03,818 952 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-27 16:51:03,818 952 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 52(Integer)
2020-08-27 16:51:03,819 953 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
Account{id=4, uid=52, money=3000.0}
User{id=52, username='Tom', birthday=Mon Oct 28 10:26:42 CST 2019, sex='男', address='广东佛山'}
======== 一对一 延迟加载 结果输出 ========
Account{id=5, uid=52, money=4000.0}
User{id=52, username='Tom', birthday=Mon Oct 28 10:26:42 CST 2019, sex='男', address='广东佛山'}
2020-08-27 16:51:03,819 953 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]
2020-08-27 16:51:03,820 954 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]

由上述结果可知,mybatis 执行了 延迟加载。它是先去查询了 account 表,然后再去查询 user 表。 如果不好理解,那就把测试方法改为如下代码:

1
2
3
4
5
@Test
public void testFindAll() {
List<Account> accounts = accountDao.findAll();
// 啥都不干
}

再次执行 testFindAll 方法, 结果如下(注意 SQL 语句)

1
2
3
4
5
6
7
2020-08-27 16:55:36,796 389    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-27 16:55:37,089 682 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]
2020-08-27 16:55:37,098 691 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Preparing: SELECT * FROM account
2020-08-27 16:55:37,133 726 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Parameters:
2020-08-27 16:55:37,223 816 [ main] DEBUG hizhan.dao.IAccountDao.findAll - <== Total: 4
2020-08-27 16:55:37,223 816 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]
2020-08-27 16:55:37,223 816 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]

由上述结果可知,mybatis 只查询了 account 表,而没有去查询 user 表。(因为没有使用到,所以不去查,这个就是延迟加载) 一对一的延迟加载实现了,那么接下来实现 一对多的延迟加载


第一步: 修改 IAccountDao 接口,新加如下方法。 代码如下:

IAccountDao.java
1
2
3
4
5
6
7
8
9
public interface IAccountDao {

// 查询所有账户,同时还需要获取到当前账户的所属用户信息
List<Account> findAll();

+ // 根据用户 id 查询账户信息
+ List<Account> findById(Integer id);

}

第二步: 修改 IUserDao.xml 文件。 修改如下:

IUserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<resultMap id="userMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>

- <!-- 配置 一对多 延迟加载 -->
+ <collection property="accounts" ofType="account" select="club.guoshizhan.dao.IAccountDao.findById" column="id">
- <collection property="accounts" ofType="account">
- <id property="id" column="aid"></id>
- <result property="uid" column="uid"></result>
- <result property="money" column="money"></result>
</collection>
</resultMap>

<select id="findAll" resultMap="userMap">
- SELECT u.*,a.id as aid,a.money,a.uid FROM USER u LEFT OUTER JOIN account a ON a.uid=u.id
+ SELECT * FROM USER
</select>

第三步: 修改 IAccountDao.xml 文件,加入如下语句。 修改如下:

IAccountDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<resultMap id="accountMap" type="account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
+ <!-- 配置一对一延迟加载,使用 select 属性指定内容 -->
+ <association property="user" column="uid" javaType="user" select="club.guoshizhan.dao.IUserDao.findById">
+ </association>
</resultMap>

+ <!-- 根据用户 id 查询账户信息 -->
+ <select id="findById" resultType="account">
+ SELECT * FROM account where uid=#{uid}
+ </select>

第四步: 新建 UserTest 测试类。 代码如下:

UserTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package club.guoshizhan.test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class UserTest {

private InputStream in;
private IUserDao userDao;
private SqlSession session;

@Before
public void init() throws Exception {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
}

@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println("======== 一对多 延迟加载 结果输出 ========");
System.out.println(user);
System.out.println(user.getAccounts());
}
}

@After
public void destroy() throws IOException {
session.close();
in.close();
}

}

执行 testFindAll 方法, 结果如下(注意 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2020-08-27 17:14:33,760 329    [           main] DEBUG .apache.ibatis.io.ResolverUtil  - Checking to see if class club.guoshizhan.dao.IAccountDao matches criteria [is assignable to Object]
2020-08-27 17:14:33,761 330 [ main] DEBUG .apache.ibatis.io.ResolverUtil - Checking to see if class club.guoshizhan.dao.IUserDao matches criteria [is assignable to Object]
2020-08-27 17:14:33,952 521 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-27 17:14:34,224 793 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]
2020-08-27 17:14:34,231 800 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - ==> Preparing: SELECT * FROM USER
2020-08-27 17:14:34,261 830 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - ==> Parameters:
2020-08-27 17:14:34,359 928 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - <== Total: 7
======== 一对多 延迟加载 结果输出 ========
2020-08-27 17:14:34,360 929 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Preparing: SELECT * FROM account where uid=?
2020-08-27 17:14:34,360 929 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Parameters: 42(Integer)
2020-08-27 17:14:34,443 1012 [ main] DEBUG izhan.dao.IAccountDao.findById - <== Total: 0
User{id=42, username='小二王', birthday=Fri Mar 02 15:09:37 CST 2018, sex='女', address='北京金燕龙'}
[]
======== 一对多 延迟加载 结果输出 ========
2020-08-27 17:14:34,446 1015 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Preparing: SELECT * FROM account where uid=?
2020-08-27 17:14:34,446 1015 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Parameters: 43(Integer)
2020-08-27 17:14:34,447 1016 [ main] DEBUG izhan.dao.IAccountDao.findById - <== Total: 0
User{id=43, username='大王', birthday=Tue Nov 12 10:19:13 CST 2019, sex='男', address='Los Angeles'}
[]
======== 一对多 延迟加载 结果输出 ========
2020-08-27 17:14:34,447 1016 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Preparing: SELECT * FROM account where uid=?
2020-08-27 17:14:34,447 1016 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Parameters: 46(Integer)
2020-08-27 17:14:34,450 1019 [ main] DEBUG izhan.dao.IAccountDao.findById - <== Total: 2
User{id=46, username='老王', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='北京'}
[Account{id=1, uid=46, money=1000.0}, Account{id=3, uid=46, money=2000.0}]
======== 一对多 延迟加载 结果输出 ========
2020-08-27 17:14:34,451 1020 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Preparing: SELECT * FROM account where uid=?
2020-08-27 17:14:34,451 1020 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Parameters: 49(Integer)
2020-08-27 17:14:34,452 1021 [ main] DEBUG izhan.dao.IAccountDao.findById - <== Total: 0
User{id=49, username='Jack', birthday=Mon Oct 28 09:15:39 CST 2019, sex='男', address='上海闵行'}
[]
======== 一对多 延迟加载 结果输出 ========
2020-08-27 17:14:34,452 1021 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Preparing: SELECT * FROM account where uid=?
2020-08-27 17:14:34,452 1021 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Parameters: 51(Integer)
2020-08-27 17:14:34,453 1022 [ main] DEBUG izhan.dao.IAccountDao.findById - <== Total: 0
User{id=51, username='Lisa', birthday=Mon Oct 28 09:17:31 CST 2019, sex='女', address='广东佛山'}
[]
======== 一对多 延迟加载 结果输出 ========
2020-08-27 17:14:34,453 1022 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Preparing: SELECT * FROM account where uid=?
2020-08-27 17:14:34,453 1022 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Parameters: 52(Integer)
2020-08-27 17:14:34,455 1024 [ main] DEBUG izhan.dao.IAccountDao.findById - <== Total: 2
User{id=52, username='Tom', birthday=Mon Oct 28 10:26:42 CST 2019, sex='男', address='广东佛山'}
[Account{id=4, uid=52, money=3000.0}, Account{id=5, uid=52, money=4000.0}]
======== 一对多 延迟加载 结果输出 ========
2020-08-27 17:14:34,455 1024 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Preparing: SELECT * FROM account where uid=?
2020-08-27 17:14:34,456 1025 [ main] DEBUG izhan.dao.IAccountDao.findById - ==> Parameters: 54(Integer)
2020-08-27 17:14:34,457 1026 [ main] DEBUG izhan.dao.IAccountDao.findById - <== Total: 0
User{id=54, username='Bob', birthday=Tue Nov 12 10:21:41 CST 2019, sex='男', address='广东佛山'}
[]
2020-08-27 17:14:34,458 1027 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]
2020-08-27 17:14:34,458 1027 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1fbf16f]

TIPS: mybatis 的延迟加载 到此结束!!!

mybatis 的缓存

缓存 是个很重要的东西,英文名叫 Cache那么缓存到底是什么呢?往下看:

1
2
3
4
5
6
7
8
9
10
11
12
13
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ 什么是缓存?
+ 缓存就是存在于内存中的临时数据。

+++ 为什么要使用缓存?
+ 减少和数据库的交互次数,提高执行效率。

+++ 什么样的数据能使用缓存,什么样的数据不能使用?
+ 适用于缓存的数据:1、经常查询并且不经常改变的。2、数据的正确与否对最终结果影响不大的。
- 不适用于缓存的数据:1、经常改变的数据。2、数据的正确与否对最终结果影响很大的数据。例如:商品的库存,银行的汇率,股市的牌价。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

了解完上述问题之后,我们来了解一下 mybatis 中的 一级缓存二级缓存 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ Mybatis 中的一级缓存和二级缓存
+ 一级缓存:它指的是 Mybatis 中 SqlSession 对象的缓存。当我们执行查询之后,查询的结果会同时存入到 SqlSession 为我们提供一块区域中。
+ 该区域的结构是一个 Map 集合。当我们再次查询同样的数据,Mybatis 会先去 SqlSession 中查询是否有,有的话直接拿出来用。
- 当 SqlSession 对象消失时,Mybatis 的一级缓存也就消失了。

+ 二级缓存:它指的是 Mybatis 中 SqlSessionFactory 对象的缓存。由同一个 SqlSessionFactory 对象创建的 SqlSession 共享其缓存。
+++ 二级缓存的使用步骤:
+ 第一步:让 Mybatis 框架支持二级缓存(在 SqlMapConfig.xml 中配置)
+ 第二步:让当前的映射文件支持二级缓存(在 IUserDao.xml 中配置)
+ 第三步:让当前的操作支持二级缓存(在 select 标签中配置)

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

现在我们测试一下 一级缓存具体操作:把 MybatisTest 类中的 testFindById 方法修改成下列代码:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
// 02-根据 id 查询用户
@Test
public void testFindById() {
User user = userDao.findById(52);
System.out.println(user);

User user1 = userDao.findById(52);
System.out.println(user1);

System.out.println(user==user1);
}

执行 testFindById 方法, 结果如下(关注 user==user1 的结果及 SQL 语句):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2020-08-25 15:53:49,849 212    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-25 15:53:50,079 442 [ main] DEBUG source.pooled.PooledDataSource - Created connection 9807749.
2020-08-25 15:53:50,079 442 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 15:53:50,084 447 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-25 15:53:50,125 488 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 52(Integer)
2020-08-25 15:53:50,211 574 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
2020-08-25 15:53:50,212 575 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 15:53:50,212 575 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 52(Integer)
2020-08-25 15:53:50,213 576 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 0
User{userId=52, userName='Tom', userBirthday=Mon Oct 28 10:26:42 CST 2019, userSex='男', userAddress='广东佛山'}
User{userId=52, userName='Tom', userBirthday=Mon Oct 28 10:26:42 CST 2019, userSex='男', userAddress='广东佛山'}
true
2020-08-25 15:53:50,221 584 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 15:53:50,222 585 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 15:53:50,222 585 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 9807749 to pool.

由上述 user==user1 的结果为 true 可知,mybatis 是使用了 一级缓存 的。这也说明了 mybatis 的一级缓存不需要进行配置,是默认就有的。 那么问题来了,如果我们想要关闭一级缓存, 那该如何去做呢?接着往下看!!!


MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 02-根据 id 查询用户
@Test
public void testFindById() {
User user = userDao.findById(52);
System.out.println(user);
session.close(); // 关闭 SqlSession 对象,即关闭一级缓存
// session.clearCache(); // clearCache 也可以实现关闭一级缓存

session = factory.openSession(); // 再次获取 SqlSession 对象
userDao = session.getMapper(IUserDao.class);
User user1 = userDao.findById(52);
System.out.println(user1);

System.out.println(user==user1);
}

再次执行 testFindById 方法, 结果如下(关注 user==user1 的结果及 SQL 语句):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2020-08-26 22:20:37,703 216    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-26 22:20:38,040 553 [ main] DEBUG source.pooled.PooledDataSource - Created connection 5257320.
2020-08-26 22:20:38,040 553 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@503868]
2020-08-26 22:20:38,043 556 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-26 22:20:38,070 583 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 52(Integer)
2020-08-26 22:20:38,090 603 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
User{Id=52, username='Tom', birthday=Mon Oct 28 10:26:42 CST 2019, sex='男', address='广东佛山'}
2020-08-26 22:20:38,099 612 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@503868]
2020-08-26 22:20:38,099 612 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@503868]
2020-08-26 22:20:38,099 612 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 5257320 to pool.
2020-08-26 22:20:38,099 612 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-26 22:20:38,099 612 [ main] DEBUG source.pooled.PooledDataSource - Checked out connection 5257320 from pool.
2020-08-26 22:20:38,100 613 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@503868]
2020-08-26 22:20:38,100 613 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-26 22:20:38,100 613 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 52(Integer)
2020-08-26 22:20:38,101 614 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
User{Id=52, username='Tom', birthday=Mon Oct 28 10:26:42 CST 2019, sex='男', address='广东佛山'}
false

从结果可知,我们已经实现了一级缓存的关闭操作(从结果为 false ,且进行了两次查询操作可得知)。 关闭一级缓存所使用的方法是 close()clearCache() 方法,任选其一即可实现操作。


上述一级缓存搞定了,那么如果一级缓存中的数据和数据库的数据不一致了,那么如何实现数据同步呢? 先看如下代码,猜一猜执行的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testFindById() {
// 01-第一次执行查询操作
User user = userDao.findById(52);
System.out.println(user);

// 02-执行一次更新操作
user.setUsername("Tom"); // 此处更新了数据,会导致一级缓存中的数据和数据库的数据不同步
userDao.updateUser(user);

// 03-再次执行查询操作
User user1 = userDao.findById(52);
System.out.println(user1);

System.out.println(user==user1);
}

上述代码执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2020-08-26 22:38:23,855 220    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-26 22:38:24,093 458 [ main] DEBUG source.pooled.PooledDataSource - Created connection 24129702.
2020-08-26 22:38:24,093 458 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@17030a6]
2020-08-26 22:38:24,096 461 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-26 22:38:24,126 491 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 52(Integer)
2020-08-26 22:38:24,149 514 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
User{Id=52, username='Jack', birthday=Mon Oct 28 10:26:42 CST 2019, sex='男', address='广东佛山'}
2020-08-26 22:38:24,155 520 [ main] DEBUG hizhan.dao.IUserDao.updateUser - ==> Preparing: update user set username=? where id=?
2020-08-26 22:38:24,156 521 [ main] DEBUG hizhan.dao.IUserDao.updateUser - ==> Parameters: Tom(String), 52(Integer)
2020-08-26 22:38:24,156 521 [ main] DEBUG hizhan.dao.IUserDao.updateUser - <== Updates: 1
2020-08-26 22:38:24,157 522 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-26 22:38:24,157 522 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 52(Integer)
2020-08-26 22:38:24,158 523 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
User{Id=52, username='Tom', birthday=Mon Oct 28 10:26:42 CST 2019, sex='男', address='广东佛山'}
false
2020-08-26 22:38:24,158 523 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@17030a6]

由结果可知:如果数据发生了更新,那么再次发起查询,mybatis 就不会从一级缓存中取,而是去重新查询数据库。 其实,当我们去调用 SqlSession 的 修改、删除、添加、close()、commit() 等方法时,就会清空一级缓存。 所以,更新数据后,一级缓存仍然可以和数据库实现同步。


现在,我们去开启 二级缓存(二级缓存的介绍及相关步骤上面已经讲过了) 。具体步骤:第一步: 在主配置文件 SqlMapConfig.xml 加入如下代码(其实下列代码可加可不加,因为 mybatis 的默认配置就是 true,所以可不加,加了也没有关系):

SqlMapConfig.xml
1
2
3
4
5
6
<configuration>

+ <!-- 配置开启二级缓存 -->
+ <settings>
+ <setting name="cacheEnabled" value="true"/>
+ </settings>

第二步:IUserDao.xml 文件中加入如下配置。(表示开启二级缓存) 代码如下:

IUserDao.xml
1
2
3
4
5
6
7
8
<mapper namespace="club.guoshizhan.dao.IUserDao">

+ <!-- 开启 user 支持二级缓存 -->
+ <cache/>
+ <!-- 在 select 标签上加上 userCache="true" 表示 select 查询语句支持二级缓存 -->
+ <select id="findById" parameterType="Integer" resultType="club.guoshizhan.domain.User" useCache="true">
select * from user where id=#{id}
</select>

第三步: 在测试类中加入如下测试方法,用于测试二级缓存。 代码如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 测试二级缓存
@Test
public void testSecondLevelCache() {

SqlSession session = factory.openSession();
IUserDao userDao = session.getMapper(IUserDao.class);
User user = userDao.findById(52);
System.out.println(user);
session.close(); // 关闭一级缓存

SqlSession session1 = factory.openSession(); // 再次打开一个名为 session1 的 SqlSession 对象
IUserDao userDao1 = session1.getMapper(IUserDao.class);
User user1 = userDao1.findById(52);
System.out.println(user1);
session1.close();

}

执行 testSecondLevelCache 方法, 结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
2020-08-26 23:06:20,961 208    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-26 23:06:21,200 447 [ main] DEBUG source.pooled.PooledDataSource - Created connection 23649725.
2020-08-26 23:06:21,200 447 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@168ddbd]
2020-08-26 23:06:21,203 450 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-26 23:06:21,235 482 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 52(Integer)
2020-08-26 23:06:21,259 506 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
User{Id=52, username='Tom', birthday=Mon Oct 28 10:26:42 CST 2019, sex='男', address='广东佛山'}
2020-08-26 23:06:21,273 520 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@168ddbd]
2020-08-26 23:06:21,275 522 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@168ddbd]
2020-08-26 23:06:21,276 523 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 23649725 to pool.
2020-08-26 23:06:21,278 525 [ main] DEBUG club.guoshizhan.dao.IUserDao - Cache Hit Ratio [club.guoshizhan.dao.IUserDao]: 0.5
User{Id=52, username='Tom', birthday=Mon Oct 28 10:26:42 CST 2019, sex='男', address='广东佛山'}

由结果可知,mybatis 只发起了一次查询,由于一级缓存已经关闭了,所以第二次则是通过 二级缓存 查到的数据。

TIPS: mybatis 的缓存 到此结束!!!

mybatis 注解开发

环境搭建


mybatis 注解开发的 环境搭建 过程上述已经做过了,所以此处跳过。 详情请参考: mybatis 注解案例

注意: 使用注解的时候,不要把接口的 xml 文件也加上, 否则会报错。(哪怕你没有使用接口的 xml 文件,它也会报错
解决方法: 把接口的 xml 文件移动到不相干的目录,或者直接删除, 这样就不会报错了。


单表 CRUD 操作

CRUD 操作已经写过很多次了,基本步骤也就是那样。 所以,这里就不把每一个操作都一步一步的写,而是写一个整体的过程。 如果需要知道一步一步的操作,请参考: mybatis 的单表操作注意: 注解开发比 xml 配置更简单哦!!!


第一步: 编写 IUserDao 接口,里面定义各种单表操作的方法(注解就是在这个地方使用的)。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 01-查询所有用户
@Select("select * from user")
List<User> findAll();

// 02-保存用户
@Insert("insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})")
void saveUser(User user);

// 03-更新用户
@Update("update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}")
void updateUser(User user);

// 04-删除用户
@Delete("delete from user where id=#{id}")
void deleteUser(Integer id);

// 05-根据 id 查询用户
@Select("select * from user where id=#{id}")
User findById(Integer id);

// 06-根据用户名称模糊查询
@Select("select * from user where username like #{username}")
/* @Select("select * from user where username like '%${value}%'") */
List<User> findByName(String username);

// 07-查询总用户数
@Select("select count(id) from user")
int findTotal();

}

第二步: 编写 MybatisTest 测试类进行测试。 代码如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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.Date;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {

private InputStream in;
private IUserDao userDao;
private SqlSession session;

// 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
}

// 01-查询所有用户
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}

// 02-保存用户
@Test
public void testSave() {
User user = new User();
user.setUsername("Miracle");
user.setBirthday(new Date());
user.setSex("女");
user.setAddress("Los Angeles");
userDao.saveUser(user);
/* 记得在 destroy 方法里提交事务,否则保存不成功 */
}

// 03-更新用户
@Test
public void testUpdate() {
User user = new User();
user.setId(74);
user.setUsername("Miracle");
user.setBirthday(new Date());
user.setSex("女");
user.setAddress("Washington");
userDao.updateUser(user);
}

// 04-删除用户
@Test
public void testDelete() {
userDao.deleteUser(74);
}

// 05-根据 id 查询用户
@Test
public void testFindById() {
User user = userDao.findById(52);
System.out.println(user);
}

// 06-根据用户名称模糊查询
@Test
public void testFindByName() {
String username = "%王%";
List<User> users = userDao.findByName(username);
for (User user : users) {
System.out.println(user);
}
}

// 07-查询总用户数
@Test
public void testFindTotal() {
int total = userDao.findTotal();
System.out.println("===================== total:" + total + " =====================");
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destroy() throws IOException {
session.commit();
session.close();
in.close();
}

}

TIPS: 执行上述的任意一个测试方法,即可实现相应的功能。 但是还存在一些问题,接着往下看!!!


上述操作虽然成功,但是我们还需要来解决一个问题:如果 User 实体类属性和数据库字段不一致,会有什么后果呢? 这个问题在原先讲到过,也使用 xml 的方式解决了这个问题。但是现在是使用注解开发,那么如何解决这个问题呢? 带着这个问题,继续往下看!!!


第一步: 修改 User 类,故意把它的属性设置的和数据库字段不一致。 代码如下:

User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package club.guoshizhan.domain;

import java.io.Serializable;
import java.util.Date;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:52
* @Description: User 实体类
*/
public class User implements Serializable {

private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;

public Integer getUserId() {
return userId;
}

public void setUserId(Integer userId) {
this.userId = userId;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public Date getUserBirthday() {
return userBirthday;
}

public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}

public String getUserSex() {
return userSex;
}

public void setUserSex(String userSex) {
this.userSex = userSex;
}

public String getUserAddress() {
return userAddress;
}

public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}

@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userBirthday=" + userBirthday +
", userSex='" + userSex + '\'' +
", userAddress='" + userAddress + '\'' +
'}';
}

}

第二步: 为了解决实体类属性和数据库字段不一致的问题,我们需要修改 IUserDao 接口(解决方式见注释)。 代码如下:

IUserDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 01-查询所有用户
@Select("select * from user")
// 我们使用 @Results 注解来解决实体类属性和数据库字段不一致导致的封装无效问题
// 此处的 id 属性是一个唯一标识,相当于 resultMap 中的 id 属性。目的是此处定义好的配置可以在其他的方法上使用
@Results(id = "userMap", value = {
// 以下的 id = true 表示 id 是主键
@Result(id = true, property = "userId", column = "id"),
@Result(property = "userName", column = "username"),
@Result(property = "userAddress", column = "address"),
@Result(property = "userSex", column = "sex"),
@Result(property = "userBirthday", column = "birthday")
})
List<User> findAll();

// 02-根据 id 查询用户
@Select("select * from user where id=#{id}")
@ResultMap(value = {"userMap"})
// 此处使用了上述定义好的属性映射关系
User findById(Integer id);

// 03-根据用户名称模糊查询
@Select("select * from user where username like #{username}")
@ResultMap("userMap")
/* @Select("select * from user where username like '%${value}%'") */
List<User> findByName(String username);

}

第三步: 编写测试类进行进行测试。 代码如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {

private InputStream in;
private IUserDao userDao;
private SqlSession session;

// 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
}

// 01-查询所有用户
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}

// 02-根据 id 查询用户
@Test
public void testFindById() {
User user = userDao.findById(52);
System.out.println(user);
}

// 03-根据用户名称模糊查询
@Test
public void testFindByName() {
String username = "%王%";
List<User> users = userDao.findByName(username);
for (User user : users) {
System.out.println(user);
}
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destroy() throws IOException {
session.commit();
session.close();
in.close();
}

}

执行上述的任何一个方法,都是没有问题的,即都可以封装成功,每一个属性都是有值的。 但是,如果不加 @Results 注解 的那段相关配置,那么执行任何方法,都是无法完成封装的。我们以 testFindAll 方法为例,执行这个方法,结果如下(只有 username 能够封装成功,其余都为 null):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2020-08-25 11:22:37,059 172    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-25 11:22:37,308 421 [ main] DEBUG source.pooled.PooledDataSource - Created connection 23465806.
2020-08-25 11:22:37,309 422 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1660f4e]
2020-08-25 11:22:37,312 425 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - ==> Preparing: select * from user
2020-08-25 11:22:37,340 453 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - ==> Parameters:
2020-08-25 11:22:37,357 470 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - <== Total: 7
User{userId=null, userName='小二王', userBirthday=null, userSex='null', userAddress='null'}
User{userId=null, userName='大王', userBirthday=null, userSex='null', userAddress='null'}
User{userId=null, userName='老王', userBirthday=null, userSex='null', userAddress='null'}
User{userId=null, userName='Jack', userBirthday=null, userSex='null', userAddress='null'}
User{userId=null, userName='Lisa', userBirthday=null, userSex='null', userAddress='null'}
User{userId=null, userName='Tom', userBirthday=null, userSex='null', userAddress='null'}
User{userId=null, userName='Bob', userBirthday=null, userSex='null', userAddress='null'}
2020-08-25 11:22:37,358 471 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1660f4e]
2020-08-25 11:22:37,358 471 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1660f4e]
2020-08-25 11:22:37,359 472 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 23465806 to pool.

TIPS: 单表 CRUD 操作 到此结束!!!

多表查询操作

一对一操作

在 xml 配置中讲过了这部分, 所以这里只是简述一下。第一步: domain 包下新建 Account 实体类。 代码如下:

Account.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package club.guoshizhan.domain;

import java.io.Serializable;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 10:59
* @Description: Account 类
*/
public class Account implements Serializable {

private Integer id;
private Integer uid;
private Double money;

// 多对一(mybatis 中称之为一对一)的映射:一个账户只能属于一个用户
private User user;

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Integer getUid() {
return uid;
}

public void setUid(Integer uid) {
this.uid = uid;
}

public Double getMoney() {
return money;
}

public void setMoney(Double money) {
this.money = money;
}

@Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
'}';
}

}

第二步: dao 包下新建 IAccountDao 接口,注解也就在这个地方使用。 代码如下:

IAccountDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package club.guoshizhan.dao;

import club.guoshizhan.domain.Account;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 11:07
* @Description: Account 的 dao 接口
*/
public interface IAccountDao {

// 查询所有账户,并且获取每个账户所属的用户信息
@Select("select * from account")
@Results(id = "accountMap", value = {
@Result(id = true, property = "id", column = "id"),
@Result(property = "uid", column = "uid"),
@Result(property = "money", column = "money"),
// one 表示一对一里面的 select 属性表示对应方法的全限定类名,fetchType 表示加载时机:立即加载、延迟加载或者默认
@Result(property = "user", column = "uid",one = @One(select = "club.guoshizhan.dao.IUserDao.findById",fetchType = FetchType.EAGER))
})
List<Account> findAll();

}

第三步: SqlMapConfig 主配置文件中添加一行配置,让框架能够扫描到 IAccountDao 接口。 代码如下:

SqlMapConfig.java
1
2
3
4
<mappers>
<mapper class="club.guoshizhan.dao.IUserDao"/>
+ <mapper class="club.guoshizhan.dao.IAccountDao"/>
</mappers>

第四步: 编写 AccountTest 测试类进行测试。 代码如下:

AccountTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package club.guoshizhan.Test;

import club.guoshizhan.dao.IAccountDao;
import club.guoshizhan.domain.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class AccountTest {

private InputStream in;
private IAccountDao accountDao;
private SqlSession session;

@Before
public void init() throws Exception {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
accountDao = session.getMapper(IAccountDao.class);
}

// 查询所有用户
@Test
public void testFindAll() {
List<Account> accounts = accountDao.findAll();
for (Account account : accounts) {
System.out.println("======== 一对一表结构结果输出 ========");
System.out.println(account);
System.out.println(account.getUser());
}
}


@After
public void destroy() throws IOException {
session.close();
in.close();
}

}

执行上述 testFindAll 方法, 结果如下(注意 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2020-08-25 15:08:07,647 188    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-25 15:08:07,905 446 [ main] DEBUG source.pooled.PooledDataSource - Created connection 24030126.
2020-08-25 15:08:07,906 447 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@16eabae]
2020-08-25 15:08:07,909 450 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Preparing: select * from account
2020-08-25 15:08:07,943 484 [ main] DEBUG hizhan.dao.IAccountDao.findAll - ==> Parameters:
2020-08-25 15:08:07,961 502 [ main] DEBUG oshizhan.dao.IUserDao.findById - ====> Preparing: select * from user where id=?
2020-08-25 15:08:07,962 503 [ main] DEBUG oshizhan.dao.IUserDao.findById - ====> Parameters: 46(Integer)
2020-08-25 15:08:07,976 517 [ main] DEBUG oshizhan.dao.IUserDao.findById - <==== Total: 1
2020-08-25 15:08:07,977 518 [ main] DEBUG hizhan.dao.IAccountDao.findAll - <== Total: 2
======== 一对一表结构结果输出 ========
Account{id=1, uid=46, money=1000.0}
User{userId=46, userName='老王', userBirthday=Wed Mar 07 17:37:26 CST 2018, userSex='男', userAddress='北京'}
======== 一对一表结构结果输出 ========
Account{id=3, uid=46, money=2000.0}
User{userId=46, userName='老王', userBirthday=Wed Mar 07 17:37:26 CST 2018, userSex='男', userAddress='北京'}
2020-08-25 15:08:07,989 530 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@16eabae]
2020-08-25 15:08:07,989 530 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@16eabae]
2020-08-25 15:08:07,989 530 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 24030126 to pool.

一对多操作

第一步: User 实体类中添加如下几行代码。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package club.guoshizhan.domain;

import java.io.Serializable;
import java.util.Date;
+ import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:52
* @Description: User 实体类
*/
public class User implements Serializable {

private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;

+ // 一对多关系映射:一个用户对应多个账户
+ private List<Account> accounts;

+ public List<Account> getAccounts() {
+ return accounts;
+ }

+ public void setAccounts(List<Account> accounts) {
+ this.accounts = accounts;
+ }

public Integer getUserId() {
return userId;
}

public void setUserId(Integer userId) {
this.userId = userId;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public Date getUserBirthday() {
return userBirthday;
}

public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}

public String getUserSex() {
return userSex;
}

public void setUserSex(String userSex) {
this.userSex = userSex;
}

public String getUserAddress() {
return userAddress;
}

public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}

@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userBirthday=" + userBirthday +
", userSex='" + userSex + '\'' +
", userAddress='" + userAddress + '\'' +
'}';
}

}

第二步: IAccountDao 接口中添加如下几行代码。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package club.guoshizhan.dao;

import club.guoshizhan.domain.Account;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/8/24 11:07
* @Description: Account 的 dao 接口
*/
public interface IAccountDao {

// 查询所有账户,并且获取每个账户所属的用户信息
@Select("select * from account")
@Results(id = "accountMap", value = {
@Result(id = true, property = "id", column = "id"),
@Result(property = "uid", column = "uid"),
@Result(property = "money", column = "money"),
// one 表示一对一里面的 select 属性表示对应方法的全限定类名,fetchType 表示加载时机:立即加载、延迟加载或者默认
@Result(property = "user", column = "uid",one = @One(select = "club.guoshizhan.dao.IUserDao.findById",fetchType = FetchType.EAGER))
})
List<Account> findAll();

+ // 根据用户 id 查询账户信息
+ @Select("select * from account where uid = #{uid}")
+ List<Account> findAccountByUid(Integer uid);

}

第三步: IUserDao 接口中添加一行代码。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package club.guoshizhan.dao;

import club.guoshizhan.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/6/22 23:53
* @Description: 用户持久层接口
*/
public interface IUserDao {

// 01-查询所有用户
@Select("select * from user")
// 此处的 id 属性是一个唯一标识,相当于 resultMap 中的 id 属性。目的是此处定义好的配置可以在其他的方法上使用
@Results(id = "userMap", value = {
@Result(id = true, property = "userId", column = "id"),
@Result(property = "userName", column = "username"),
@Result(property = "userAddress", column = "address"),
@Result(property = "userSex", column = "sex"),
@Result(property = "userBirthday", column = "birthday"),
+ @Result(property = "accounts", column = "id", many = @Many(select = "club.guoshizhan.dao.IAccountDao.findAccountByUid", fetchType = FetchType.LAZY))

})
List<User> findAll();

// 02-根据 id 查询用户
@Select("select * from user where id=#{id}")
@ResultMap(value = {"userMap"})
// 此处使用了上述定义好的属性映射关系
User findById(Integer id);

// 03-根据用户名称模糊查询
@Select("select * from user where username like #{username}")
@ResultMap("userMap")
/* @Select("select * from user where username like '%${value}%'") */
List<User> findByName(String username);

}

第四步: 执行测试类中的 testFindAll 方法。 测试类代码及结果如下:

MybatisTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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;

/**
* @Author: guoshizhan
* @Create: 2020/6/23 11:03
* @Description: Mybatis 测试类
*/
public class MybatisTest {

private InputStream in;
private IUserDao userDao;
private SqlSession session;

// 测试方法执行之前执行,用于一些初始化的操作,不理解的应该去看看单元测试相关知识
@Before
public void init() throws Exception {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
}

// 01-查询所有用户
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println("======== 每个用户的信息(使用了延迟加载) ========");
System.out.println(user);
System.out.println(user.getAccounts());
}
}

// 02-根据 id 查询用户
@Test
public void testFindById() {
User user = userDao.findById(52);
System.out.println(user);
}

// 03-根据用户名称模糊查询
@Test
public void testFindByName() {
String username = "%王%";
List<User> users = userDao.findByName(username);
for (User user : users) {
System.out.println(user);
}
}

// 测试方法执行之后执行,用于释放资源,不理解的应该去看看单元测试相关知识
@After
public void destroy() throws IOException {
session.commit();
session.close();
in.close();
}

}
The result of MybatisTest's testFindAll method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2020-08-25 15:38:35,275 240    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-25 15:38:35,553 518 [ main] DEBUG source.pooled.PooledDataSource - Created connection 12309002.
2020-08-25 15:38:35,554 519 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@bbd20a]
2020-08-25 15:38:35,556 521 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - ==> Preparing: select * from user
2020-08-25 15:38:35,587 552 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - ==> Parameters:
2020-08-25 15:38:35,673 638 [ main] DEBUG uoshizhan.dao.IUserDao.findAll - <== Total: 7
======== 每个用户的信息(使用了延迟加载) ========
2020-08-25 15:38:35,674 639 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 15:38:35,674 639 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 42(Integer)
2020-08-25 15:38:35,674 639 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 0
User{userId=42, userName='小二王', userBirthday=Fri Mar 02 15:09:37 CST 2018, userSex='女', userAddress='北京金燕龙'}
[]
======== 每个用户的信息(使用了延迟加载) ========
2020-08-25 15:38:35,679 644 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 15:38:35,679 644 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 43(Integer)
2020-08-25 15:38:35,679 644 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 0
User{userId=43, userName='大王', userBirthday=Tue Nov 12 10:19:13 CST 2019, userSex='男', userAddress='Los Angeles'}
[]
======== 每个用户的信息(使用了延迟加载) ========
2020-08-25 15:38:35,680 645 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 15:38:35,680 645 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 46(Integer)
2020-08-25 15:38:35,681 646 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 2
User{userId=46, userName='老王', userBirthday=Wed Mar 07 17:37:26 CST 2018, userSex='男', userAddress='北京'}
[Account{id=1, uid=46, money=1000.0}, Account{id=3, uid=46, money=2000.0}]
======== 每个用户的信息(使用了延迟加载) ========
2020-08-25 15:38:35,681 646 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 15:38:35,682 647 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 49(Integer)
2020-08-25 15:38:35,682 647 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 0
User{userId=49, userName='Jack', userBirthday=Mon Oct 28 09:15:39 CST 2019, userSex='男', userAddress='上海闵行'}
[]
======== 每个用户的信息(使用了延迟加载) ========
2020-08-25 15:38:35,683 648 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 15:38:35,683 648 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 51(Integer)
2020-08-25 15:38:35,683 648 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 0
User{userId=51, userName='Lisa', userBirthday=Mon Oct 28 09:17:31 CST 2019, userSex='女', userAddress='广东佛山'}
[]
======== 每个用户的信息(使用了延迟加载) ========
2020-08-25 15:38:35,684 649 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 15:38:35,684 649 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 52(Integer)
2020-08-25 15:38:35,685 650 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 0
User{userId=52, userName='Tom', userBirthday=Mon Oct 28 10:26:42 CST 2019, userSex='男', userAddress='广东佛山'}
[]
======== 每个用户的信息(使用了延迟加载) ========
2020-08-25 15:38:35,686 651 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 15:38:35,687 652 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 54(Integer)
2020-08-25 15:38:35,688 653 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 0
User{userId=54, userName='Bob', userBirthday=Tue Nov 12 10:21:41 CST 2019, userSex='男', userAddress='广东佛山'}
[]
2020-08-25 15:38:35,688 653 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@bbd20a]
2020-08-25 15:38:35,689 654 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@bbd20a]
2020-08-25 15:38:35,689 654 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 12309002 to pool.

缓存的配置

一级缓存 不用我们开启,也不用我们配置,mabatis 已经自动配置好了,不需要我们去操心。 举个例子,把 MybatisTest 类中的 testFindById 方法修改成下列代码:

MybatisTest's testFindById method
1
2
3
4
5
6
7
8
9
10
11
// 02-根据 id 查询用户
@Test
public void testFindById() {
User user = userDao.findById(52);
System.out.println(user);

User user1 = userDao.findById(52);
System.out.println(user1);

System.out.println(user==user1);
}

执行 testFindById 方法, 结果如下(关注 user==user1 的结果及 SQL 语句)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2020-08-25 15:53:49,849 212    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-25 15:53:50,079 442 [ main] DEBUG source.pooled.PooledDataSource - Created connection 9807749.
2020-08-25 15:53:50,079 442 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 15:53:50,084 447 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-25 15:53:50,125 488 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 52(Integer)
2020-08-25 15:53:50,211 574 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
2020-08-25 15:53:50,212 575 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 15:53:50,212 575 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 52(Integer)
2020-08-25 15:53:50,213 576 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 0
User{userId=52, userName='Tom', userBirthday=Mon Oct 28 10:26:42 CST 2019, userSex='男', userAddress='广东佛山'}
User{userId=52, userName='Tom', userBirthday=Mon Oct 28 10:26:42 CST 2019, userSex='男', userAddress='广东佛山'}
true
2020-08-25 15:53:50,221 584 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 15:53:50,222 585 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 15:53:50,222 585 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 9807749 to pool.

由结果可知,user 和 user1 是相等的。所以我们需要关注的是: 二级缓存 如何去配置。 那么就来操作一下。第一步: 在测试的包下新建 SecondLevelCacheTest 测试类, 代码如下:

SecondLevelCacheTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package club.guoshizhan.Test;

import club.guoshizhan.dao.IUserDao;
import club.guoshizhan.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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;

/**
* @Author: guoshizhan
* @Create: 2020/8/25 16:01
* @Description: 二级缓存测试类
*/
public class SecondLevelCacheTest {

private InputStream in;
private SqlSessionFactory factory;
@Before
public void init() throws Exception {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(in);
}

// 测试二级缓存
@Test
public void testFindById() {

SqlSession session = factory.openSession();
IUserDao userDao = session.getMapper(IUserDao.class);
User user = userDao.findById(52);
System.out.println(user);
session.close(); // 释放一级缓存

SqlSession session1 = factory.openSession(); // 再次打开一个名为 session1 的 SqlSession 对象
IUserDao userDao1 = session1.getMapper(IUserDao.class);
User user1 = userDao1.findById(52);
System.out.println(user1);
session1.close();

}

@After
public void destroy() throws IOException {
in.close();
}

}

执行 testFindById 方法, 结果如下(由于没有配置缓存,SQL 必定执行两次。因为这样,所以需要配置二级缓存)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
20-08-25 16:10:49,746 249    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-25 16:10:49,984 487 [ main] DEBUG source.pooled.PooledDataSource - Created connection 9807749.
2020-08-25 16:10:49,985 488 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 16:10:49,987 490 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-25 16:10:50,016 519 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 52(Integer)
2020-08-25 16:10:50,091 594 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
2020-08-25 16:10:50,092 595 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 16:10:50,092 595 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 52(Integer)
2020-08-25 16:10:50,092 595 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 0
User{userId=52, userName='Tom', userBirthday=Mon Oct 28 10:26:42 CST 2019, userSex='男', userAddress='广东佛山'}
2020-08-25 16:10:50,095 598 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 16:10:50,095 598 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 16:10:50,095 598 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 9807749 to pool.
2020-08-25 16:10:50,095 598 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-08-25 16:10:50,095 598 [ main] DEBUG source.pooled.PooledDataSource - Checked out connection 9807749 from pool.
2020-08-25 16:10:50,095 598 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 16:10:50,096 599 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-25 16:10:50,096 599 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 52(Integer)
2020-08-25 16:10:50,097 600 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
2020-08-25 16:10:50,098 601 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 16:10:50,098 601 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 52(Integer)
2020-08-25 16:10:50,098 601 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 0
User{userId=52, userName='Tom', userBirthday=Mon Oct 28 10:26:42 CST 2019, userSex='男', userAddress='广东佛山'}
2020-08-25 16:10:50,099 602 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 16:10:50,099 602 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@95a785]
2020-08-25 16:10:50,099 602 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 9807749 to pool.

既然这样,那么我们如何去配置 二级缓存 呢? 第一步: 在主配置文件 SqlMapConfig.xml 加入如下代码(其实下列代码可加可不加,因为 mybatis 的默认配置就是 true,所以可不加,加了也没有关系):

SqlMapConfig.xml
1
2
3
4
5
6
<configuration>

+ <!-- 配置开启二级缓存 -->
+ <settings>
+ <setting name="cacheEnabled" value="true"/>
+ </settings>

第二步: IUserDao 接口中加入如下注解。(表示开启二级缓存) 代码如下:

IUserDao.java
1
2
3
+ @CacheNamespace(blocking = true)
public interface IUserDao {
...

上述步骤做完后,我们就去执行 testFindById 方法, 测试结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2020-08-25 16:20:36,351 231    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2020-08-25 16:20:36,644 524 [ main] DEBUG source.pooled.PooledDataSource - Created connection 26174884.
2020-08-25 16:20:36,644 524 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@18f65a4]
2020-08-25 16:20:36,647 527 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Preparing: select * from user where id=?
2020-08-25 16:20:36,677 557 [ main] DEBUG oshizhan.dao.IUserDao.findById - ==> Parameters: 52(Integer)
2020-08-25 16:20:36,773 653 [ main] DEBUG oshizhan.dao.IUserDao.findById - <== Total: 1
2020-08-25 16:20:36,775 655 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid = ?
2020-08-25 16:20:36,775 655 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 52(Integer)
2020-08-25 16:20:36,775 655 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 0
User{userId=52, userName='Tom', userBirthday=Mon Oct 28 10:26:42 CST 2019, userSex='男', userAddress='广东佛山'}
2020-08-25 16:20:36,792 672 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@18f65a4]
2020-08-25 16:20:36,792 672 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@18f65a4]
2020-08-25 16:20:36,792 672 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 26174884 to pool.
2020-08-25 16:20:36,794 674 [ main] DEBUG club.guoshizhan.dao.IUserDao - Cache Hit Ratio [club.guoshizhan.dao.IUserDao]: 0.5
User{userId=52, userName='Tom', userBirthday=Mon Oct 28 10:26:42 CST 2019, userSex='男', userAddress='广东佛山'}

我们从结果发现: 它只执行了一次 SQL 语句,并没有发起第二次查询,这说明 二级缓存 开启了,也就是第二次的查询操作使用了我们配置的二级缓存。 到此为止,mybatis 教程就结束了。如有错误,请评论区指出!!!

  

📚 本站推荐文章
  👉 从 0 开始搭建 Hexo 博客
  👉 计算机网络入门教程
  👉 数据结构入门
  👉 算法入门
  👉 IDEA 入门教程

可在评论区留言哦

一言句子获取中...

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×