MyBatis框架(2) —— 自定义MyBatis相关的类和接口

简介

  • 我们知道创建Maven项目后,在项目中的pom.xml配置文件中引入MyBatis依赖之后,我们就可以使用MyBatis相关的类和接口和相应的功能。
  • 但为了对MyBatis的体系结构和功能有更加深刻的理解和全面的认识,本文Maven项目中的pom.xml配置文件将不再引入MyBatis依赖,转而通过自定义编写MyBatis相关的类和接口来实现同样的功能。
  • MyBatis相关类和接口的介绍流程按照 测试类 中的操作步骤为顺序,先用到的类和接口先出现。

思维导图

环境搭建

Maven项目依赖

  • 在pom.xml 文件中,并不引入MyBatis框架的依赖:
    • mysql-connector-java(数据库连接)
    • junit (单元测试)
    • log4j(日志文件)
    • dom4j(解析xml文件)
    • jaxen(dom4j的依赖)
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"?>
<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>cn.water</groupId>
<artifactId>section02_MyPom</artifactId>
<version>1.0-SNAPSHOT</version>


<!-- 此处没有引入MyBatis的依赖 -->
<dependencies>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<!-- jog4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<!-- dom4j:解析xml文件 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- jaxen:dom4j的依赖 -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>

</project>

创建数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#创建数据库
CREATE DATABASE mybatis;
USE mybatis;

#创建表
CREATE TABLE USER(
id INT(11) PRIMARY KEY 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 '地址'
) ENGINE = INNODB DEFAULT CHARSET = utf8;

#插入数据
INSERT INTO USER 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','女','北京修正');

目录结构

  • src/main
    • java
      • cn/water/dao
        • UserAnnoDao.java(持久层接口)
        • UserDao.java(持久层接口)
      • cn/water/domain
        • User.java(实体类)
      • cn/water/mybatis
        • cfg
          • Configuration.java(实体类)
          • Mapper.java(实体类)
        • defaults
          • DeaultSqlSession.java(产品实现类)
          • DeaultSqlSessionFactory.java(工厂实现类)
        • io
          • Resources.java(AsStream类)
        • proxy
          • MapperProxy.java(代理增强类)
        • session
          • SqlSession.java(产品接口)
          • SqlSessionFactory.java(工厂接口)
          • SqlSessionFactoryBuilder.java(工厂构建者)
        • Utils
          • DataSourceUtil.java(数据库连接对象工具类)
          • Executor.java(数据库操作工具类)
          • XMLConfigBuilder.java(封装信息工具类)
    • resources
      • cn/water/dao
        • UserDao.xml(持久层接口的映射配置文件)
      • SqlMapConfig.xml(MyBatis主配置文件)
  • src/tese
    • java
      • cn/water/test
        • MybatisTest.java(测试文件)
  • pom.xml

数据库表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#创建数据库
CREATE DATABASE eesy_mybatis;
USE eesy_mybatis;

#创建表
CREATE TABLE USER(
id INT(11) PRIMARY KEY 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 '地址'
) ENGINE = INNODB DEFAULT CHARSET = utf8;

#插入数据
INSERT INTO USER 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','女','北京修正');

MyBatis主配置文件

  • 提取 MyBatis主配置文件中映射信息的方式是读取 mapper标签的属性,所以 mappers 标签中最好只设置一种 mapper标签,resource 或是 class。

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

<!-- 数据库连接信息 -->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>

<!-- 映射信息 -->
<mappers>
<!-- <mapper resource="cn/water/dao/UserDao.xml"></mapper>-->
<mapper class="cn.water.dao.UserAnnoDao"></mapper>
</mappers>

</configuration>

映射配置文件

UserDao.java

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>

<mapper namespace="cn.water.dao.UserDao">
<select id="findAll" resultType="cn.water.domain.User">
SELECT * FROM user;
</select>
</mapper>

实体类

  • 封装数据库表的信息

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
package cn.water.domain;

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

public class User implements Serializable {

private Integer id;
private String username;
private Date birthday;
private String sex;
private String 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;
}

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

持久层接口

  • xml

UserDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.water.dao;

import cn.water.domain.User;

import java.util.List;

public interface UserDao {

/**
* 查询所有
* @return
*/
List<User> findAll();

}
  • 注解

UserAnnoDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.water.dao;

import cn.water.Anno.Select;
import cn.water.domain.User;

import java.util.List;

public interface UserAnnoDao {

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

}

Mapper实体类

  • 成员变量
    • 结果集全类名
    • SQL语句

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
30
31
package cn.water.mybatis.cfg;

public class Mapper {

private String queryString;
private String resultType;

@Override
public String toString() {
return "Mapper{" +
"queryString='" + queryString + '\'' +
", resultType='" + 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;
}
}

Configuration实体类

  • 封装了 数据库连接信息+映射信息 的实体类
  • 成员变量
    • 数据库连接信息
      • drive
      • URL
      • username
      • password
    • 映射信息
      • Mappers
        • String
          • 持久层全类名
          • 方法名
        • Mapper
          • 结果集全类名
          • SQL语句

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
60
61
62
63
64
65
66
67
68
package cn.water.mybatis.cfg;

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


public class Configuration {

/* 数据库连接信息 */
private String driver;
private String url;
private String username;
private String password;
/* 映射信息 */
private Map<String,Mapper> mappers = new HashMap<String, Mapper>();

@Override
public String toString() {
return "Configuration{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
", mappers=" + mappers +
'}';
}

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

public void setMappers(Map<String, Mapper> mappers) {
// this.mappers = mappers; 覆盖:Map集合中永远只会有一个键值对
this.mappers.putAll(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;
}
}

**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
package cn.water.test;

import cn.water.dao.UserDao;
import cn.water.domain.User;
import cn.water.mybatis.io.Resources;
import cn.water.mybatis.session.SqlSession;
import cn.water.mybatis.session.SqlSessionFactory;
import cn.water.mybatis.session.SqlSessionFactoryBuilder;

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

public class MybatisTest {

public static void main(String[] args) throws IOException {
/* 读取配置文件 */
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
/* 创建SqlSessionFactoryBuilder工厂实现类 */
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
/* 使用IO流和工厂实现类 创建SqlSessionFactory工厂接口 */
SqlSessionFactory factory = builder.build(inputStream);
/* 使用工厂 生产SqlSession对象 */
SqlSession sqlSession = factory.openSession();
/* 使用SqlSession对象 创建Dao接口的代理对象 */
UserDao dao = sqlSession.getMapper(UserDao.class);
// UserAnnoDao dao = sqlSession.getMapper(UserAnnoDao.class);
/* 使用代理对象 执行方法 */
List<User> list = dao.findAll();
/* 遍历 */
for (User user : list) {
System.out.println(user);
}
/* 释放资源 */
sqlSession.close();
inputStream.close();

}
}

Resources

  • 输入 “SqlMapConfig.xml”,返回 InputStream

Resources.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.water.mybatis.io;

import java.io.InputStream;

public class Resources {

public static InputStream getResourceAsStream(String filePath) {
// 1.获取类字节码文件
// 2.获取加载器对象
// 3.读取配置文件路劲
// filePath 不以’/'开头时默认是从此类所在的包下取资源
// filePath 以’/'开头则是从ClassPath根下获取
return Resources.class.getClassLoader().getResourceAsStream(filePath);

}
}

SqlSessionFactoryBuilder工厂构建者

  • 返回 DefaultSqlSessionFactory工厂实现类

SqlSessionFactoryBuilder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.water.mybatis.session;

import cn.water.mybatis.Utils.XMLConfigBuilder;
import cn.water.mybatis.cfg.Configuration;
import cn.water.mybatis.defaults.DefaultSqlSessionFactory;

import java.io.InputStream;

public class SqlSessionFactoryBuilder {

/** 根据MyBatis配置文件路径的字节输入流,返回工厂实现类 */
public SqlSessionFactory build(InputStream config) {
/* 读取MyBatis配置文件,将数据封装进实体类 */
Configuration configuration = XMLConfigBuilder.loadConfiguration(config);
/* 返回值 */
return new DefaultSqlSessionFactory(configuration);
}

}

XMLConfigBuilder工具类

  • 工具类直接使用即可!
  • 用于解析MyBatis主配置文件、xml映射配置文件、存在注释的持久层接口,并将数据封装至 Configuration实体类中。

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
206
207
208
209
package cn.water.mybatis.Utils;

import cn.water.Anno.Select;
import cn.water.mybatis.io.Resources;
import cn.water.mybatis.cfg.Configuration;
import cn.water.mybatis.cfg.Mapper;
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 {



/** 解析MyBatis主配置文件 */
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();//获取属性的值"com/itheima/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的必要信息
* @param daoClassPath 持久层接口的位置
* @return
*/
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;
}



}

SqlSessionFactory工厂接口

  • 生产 DefaultSqlSession对象,为了降低耦合度。

SqlSessionFactory.java

1
2
3
4
5
6
7
8
package cn.water.mybatis.session;

public interface SqlSessionFactory {

/** 生产 操作数据库对象 */
public abstract SqlSession openSession();

}

DefaultSqlSessionFactory工厂实现类

DefaultSqlSessionFactory.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
package cn.water.mybatis.defaults;

import cn.water.mybatis.cfg.Configuration;
import cn.water.mybatis.session.SqlSession;
import cn.water.mybatis.session.SqlSessionFactory;

public class DefaultSqlSessionFactory implements SqlSessionFactory {

/** 成员变量 */
/* 数据库连接信息 + 映射信息 */
private Configuration configuration;

/** 带参构造 */
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}

/** 生产 操作数据库对象 */
public SqlSession openSession() {

return new DefaultSqlSession(configuration);
}

}

SqlSession产品接口

  • 生产 持久层接口的代理对象

SqlSession.java

1
2
3
4
5
6
7
8
9
10
11
package cn.water.mybatis.session;

public interface SqlSession {

/** 根据参数,创建代理对象 */
/* <T> T 返回值为泛型时,需要先声明再使用。 */
<T> T getMapper(Class<T> daoInterface);

/** 释放资源 */
void close();
}

DefaultSqlSession产品实现类

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
40
41
42
43
44
45
46
47
48
package cn.water.mybatis.defaults;

import cn.water.mybatis.Utils.DataSourceUtil;
import cn.water.mybatis.cfg.Configuration;
import cn.water.mybatis.proxy.MapperProxy;
import cn.water.mybatis.session.SqlSession;

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

public class DefaultSqlSession implements SqlSession {

/** 成员变量 */
/* 数据库连接信息 + 映射信息 */
private Configuration configuration;
/* 数据库连接对象 */
private Connection connection;

/** 带参构造 */
/* 初始化 configuration 和 connection */
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
connection = DataSourceUtil.getConnection(configuration);

}

/** 创建代理对象 */
public <T> T getMapper(Class<T> daoInterface) {
return (T) Proxy.newProxyInstance(
daoInterface.getClassLoader(), // 持久层接口的类加载器
new Class[]{daoInterface}, // 持久层接口
new MapperProxy(configuration.getMappers(), connection));
}

/** 释放资源 */
public void close() {
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}


}

DataSourceUtils工具类

  • 创建 Connection对象

DataSourceUtils.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 cn.water.mybatis.Utils;

import cn.water.mybatis.cfg.Configuration;

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

public class DataSourceUtil {

/** 根据实体类中的连接数据库信息,创建数据库连接对象 */
public static Connection getConnection(Configuration configuration) {
try {
/* 加载 数据库驱动 */
Class.forName(configuration.getDriver());
/* 返回值 */
return DriverManager.getConnection(configuration.getUrl(),configuration.getUsername(),configuration.getUsername());
} catch (Exception e) {
throw new RuntimeException(e);
}

}
}

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
package cn.water.mybatis.proxy;

import cn.water.mybatis.Utils.Executor;
import cn.water.mybatis.cfg.Mapper;

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


public class MapperProxy implements InvocationHandler {

/**
* 私有成员变量
* key:全类名+方法名
* value:SQL语句+结果集数据类型的全类名
*/
private Map<String, Mapper> mappers;
private Connection connection;


/** 带参构造 */
public MapperProxy(Map<String, Mapper> mappers, Connection connection) {
this.mappers = mappers;
this.connection = connection;
}

/** 代码增强 */
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("传入的参数有误!");
}
// 6.调用工具类执行查询所有
return new Executor().selectList(mapper,connection);
}
}

Executor工具类

  • 执行SQL语句,并且封装结果集

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
79
80
81
package cn.water.mybatis.Utils;

import cn.water.mybatis.cfg.Mapper;

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();//com.itheima.domain.User
Class domainClass = Class.forName(resultType);
//2.获取PreparedStatement对象
pstm = conn.prepareStatement(queryString);
//3.执行SQL语句,获取结果集
rs = pstm.executeQuery();
//4.封装结果集
List<E> list = new ArrayList<E>();//定义返回值
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配置文件中的 mapper标签测试类中代理对象的接口字节码

xml

1
2
3
4
5
6
7
使用的是XML
User{id=41, username='老王', birthday=2018-02-27 17:47:08.0, sex='男', address='北京'}
User{id=42, username='小二王', birthday=2018-03-02 15:09:37.0, sex='女', address='北京金燕龙'}
User{id=43, username='小二王', birthday=2018-03-04 11:34:34.0, sex='女', address='北京金燕龙'}
User{id=45, username='传智播客', birthday=2018-03-04 12:04:06.0, sex='男', address='北京金燕龙'}
User{id=46, username='老王', birthday=2018-03-07 17:37:26.0, sex='男', address='北京'}
User{id=48, username='小马宝莉', birthday=2018-03-08 11:44:00.0, sex='女', address='北京修正'}

注解

1
2
3
4
5
6
7
使用的是注解
User{id=41, username='老王', birthday=2018-02-27 17:47:08.0, sex='男', address='北京'}
User{id=42, username='小二王', birthday=2018-03-02 15:09:37.0, sex='女', address='北京金燕龙'}
User{id=43, username='小二王', birthday=2018-03-04 11:34:34.0, sex='女', address='北京金燕龙'}
User{id=45, username='传智播客', birthday=2018-03-04 12:04:06.0, sex='男', address='北京金燕龙'}
User{id=46, username='老王', birthday=2018-03-07 17:37:26.0, sex='男', address='北京'}
User{id=48, username='小马宝莉', birthday=2018-03-08 11:44:00.0, sex='女', address='北京修正'}
-------------本文结束-------------
Donate comment here