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 的基本配置

mybatis 的 CRUD

  

📚 本站推荐文章
  👉 从 0 开始搭建 Hexo 博客
  👉 计算机网络入门教程
  👉 数据结构入门
  👉 算法入门
  👉 IDEA 入门教程

可在评论区留言哦

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×