快速理解Mybatis——仿框架实现数据库查询操作 1 前言

1 前言

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

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

采用ORM(Object Relational Mapping)思想解决了实体和数据库映射的问题,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。

评论领取源码

2 Mybatis框架快速入门

2.1 搭建Mybatis开发环境(使用.xml配置)

数据库准备

1
2
3
4
5
6
7
8
9
10
11
12
mysql复制代码USE DATABASE eesy;

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','女','北京修正');

2.1.1 添加Mybatis3.4.5的坐标

首先创建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
xml复制代码<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</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>

2.1.2 数据库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
java复制代码package 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;
}
}

2.1.3 dao层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码package dao;

import domain.User;

import java.util.List;

public interface UserDao {

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

2.1.4 编写Dao层接口的映射文件UserDao.xml

创建位置:必须和dao接口在相同的包中。

名称:必须以持久层接口名称命名文件名,扩展名是.xml

)

mapper标签namespace属性的取值必须是dao接口的全限定类名;

操作配置(select),id属性的取值必须是dao接口的方法名

1
2
3
4
5
6
7
8
9
10
xml复制代码<?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="dao.UserDao">
<!-- 配置查询所有操作,查询结果封装在User类里 -->
<select id="findAll" resultType="domain.User">
select * from user;
</select>
</mapper>

2.1.5 编写 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
xml复制代码<?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">
<!--mybatis的主配置文件-->
<configuration>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql的环境-->
<environment id="mysql">
<!--配置事务类型-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<!--配置连接数据库的4个基本信息-->
<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>

<!-- 告知mybatis映射配置的位置 -->
<mappers>
<mapper resource="dao/UserDao.xml"/>
</mappers>
</configuration>

2.1.6 测试类

程序套路比较固定,基本步骤如下:

  1. 读取配置文件
  2. 创建SqlSessionFactory工厂
  3. 创建SqlSession
  4. 创建dao接口的代理对象 getMapper()
  5. 执行dao中的方法
  6. 释放资源
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
java复制代码package test;

import dao.UserDao;
import 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.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisTest {

public static void main(String[] args) throws IOException {
// 读取配置文件
InputStream in = Resources.getResourceAsStream("sqlMapConfig.xml");
// 创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
// 使用工厂生产SqlSession
SqlSession session = factory.openSession();
// 使用SqlSession创建Dao接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
// 使用代理对象执行dao中的方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
// 释放资源
session.close();
in.close();
}
}

2.2 使用注解开发方法

使用注解时,就不需要 resources/dao/UserDao.xml 文件了

在dao接口的方法上使用@Select注解,并且指定SQL语句

UserDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码package dao;

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

import java.util.List;

public interface UserDao {

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

SqlMapConfig.xml 全局配置文件里,如果使用注解来配置的话,应该是用class属性指定被注解的dao全限定类名

1
2
3
xml复制代码<mappers>
<mapper class="dao.UserDao"/>
</mappers>

2.3 设计模式分析

在编写测试类时,按照了六个步骤来进行的,其中涉及到了构建者、工厂、代理等设计模式。

1
2
java复制代码SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);

创建工厂时使用了构建者模式,可以把对象的创建细节隐藏,使得使用者直接调用方法即可拿到对象。

1
java复制代码SqlSession session = factory.openSession();

生产SqlSession使用了工厂模式,可以降低类之间的依赖关系(解耦)。

1
java复制代码UserDao userDao = session.getMapper(UserDao.class);

创建Dao接口实现类使用了代理模式,可以在不修改源码的基础上对已有方法增强。

注意:

实际应用时,绝对路径和相对路径都不可用,(在部署成webapp后src目录没了)

所以实际中只用两种方法:

  1. 类加载器,但只能读取类路径的配置文件
  2. 使用ServletContext的getRealPath()获得绝对路径

mybatis在使用代理dao的方式实现增删改查时:

  1. 创建代理对象
  2. 在代理对象中调用selectList

3 自定义Mybatis框架

3.1 查询所有分析

selectList方法大致分为五步:

  1. 根据配置文件创建Connection对象
  2. 获取预处理对象PreparedStatement
  3. 执行查询操作
  4. 遍历查询结果集并封装为List
  5. 返回List对象

其中第一步需要配置文件SqlMapConfig.xml中的信息,解析xml的技术已经成熟,这里不做赘述。

1
2
3
4
xml复制代码<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"/>

第二步获取预处理对象PreparedStatement

1
java复制代码conn.prepareStatement(queryString);

需要传入sql语句,需要用到UserDao.xml中定义的sql语句

1
2
3
4
5
6
xml复制代码<mapper namespace="dao.UserDao">
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="domain.User">
select * from user;
</select>
</mapper>

第四步进行数据的封装,需要知道数据库中存储的数据对应的java中的类

对应上面代码中的 resultType

纵观整个解析的过程,如果想要让selectList方法执行,还必须要提供映射信息,映射信息包含两部分:sql和封装结果的实体类名,可以将这两个信息封装成一个映射类Mapper作为map的value,其中map的key为类似于”dao.UserDao.findAll”的字符串。

具体流程如下图:

)

总结一下:

SqlMapConfig.xml 中包含连接数据库的信息、映射配置信息

UserDao.xml 中包含sql语句和封装的实体类信息

可以使用dom4j技术来解析xml

3.2 创建代理对象的分析

1
2
java复制代码// 使用SqlSession创建Dao接口的代理对象
UserDao userDao = (UserDao) session.getMapper(UserDao.class);

getMapper的具体实现如下:

1
2
3
4
5
java复制代码public <T> T getMapper(Class<T> daoInterfaceClass) {
return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
new Class[]{daoInterfaceClass},
new MapperProxy(cfg.getMappers(), connection));
}

使用代理

第一个参数是类加载器:使用和被代理对象相同的类加载器

第二个参数是代理对象要实现的接口:和被代理对象实现相同的接口

第三个参数是如何代理:我们自己提供一个增强的方法

3.3 使用Mybatis时用到的类(接口)

class Resources

class SqlSessionFactoryBuilder

interface SqlSessionFactory

interface SqlSession

3.4 准备工作

将之前写的Mybatis测试类中所需要的方法创建出来

)

3.4.1 Resources 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码package mybatis.io;

import java.io.InputStream;

/**
* 使用类加载器读取配置文件的类
*/
public class Resources {

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

}

3.4.2 SqlSession 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码package mybatis.sqlsession;

/**
* 自定义Mybatis中和数据库交互的核心类
* 里面可以创建dao接口的代理对象
*/
public interface SqlSession<T> {

/**
* 很具参数创建一个代理对象
* @param daoInterfaceClass dao的接口字节码
* @return
*/
T getMapper(Class<T> daoInterfaceClass);

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

3.4.3 SqlSessionFactory 接口

1
2
3
4
5
6
7
8
9
10
java复制代码package mybatis.sqlsession;

public interface SqlSessionFactory {

/**
* 用于打开一个新SqlSession对象
* @return
*/
SqlSession openSession();
}

3.4.4 SqlSessionFactoryBuilder 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码package mybatis.sqlsession;

import java.io.InputStream;

/**
* 用于创建SqlSessionFactory对象
*/
public class SqlSessionFactoryBuilder {

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

}

3.5 解析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
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
java复制代码package mybatis.utils;

import mybatis.io.Resources;
import mybatis.cfg.Configuration;
import 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 {

/**
* 解析主配置文件,把里面的内容填充到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();//获取属性的值"dao/IUserDao.xml"
//把映射配置文件的内容获取出来,封装成一个map
Map<String,Mapper> mappers = loadMapperConfiguration(mapperPath);
//给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();
}
}
}

需要在 pom.xml 中导入 dom4j、jaxen

1
2
3
4
5
6
7
8
9
10
xml复制代码<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>

3.5.1 在 mybatis.cfg 包下创建Configuration类和Mapper类

Configuration类中存储了”dao.UserDao.findAll”与一个Mapper对象的映射

Mapper对象中封装 执行的SQL语句 以及 结果类型的全限定类名

由于可能有多条sql语句,所以Configuration中的setMappers方法应该不断地往map中追加数据,而不是替换,使用 putAll 追加数据时必须先把map new出来,不然空指针。

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
java复制代码package mybatis.cfg;

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>();

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;
}

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

public void setMappers(Map<String, Mapper> mappers) {
this.mappers.putAll(mappers); // 追加方式添加至map
}
}

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
java复制代码package mybatis.cfg;

/**
* 用于封装 执行的SQL语句 以及 结果类型的全限定类名
*/
public class Mapper {

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

public String getQuerryString() {
return queryString;
}

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

public String getResultType() {
return resultType;
}

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

3.6 SqlSessionFactoryBuilder 类的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码package mybatis.sqlsession;

import mybatis.cfg.Configuration;
import mybatis.sqlsession.defaults.DefaultSqlSessionFactory;
import mybatis.utils.XMLConfigBuilder;

import java.io.InputStream;
/**
* 用于创建SqlSessionFactory对象
*/
public class SqlSessionFactoryBuilder {

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

SqlSessionFactoryBuilder 类中有一个 public SqlSessionFactory build(InputStream config)方法,需要返回一个SqlSessionFactory的对象。

3.6.1 SqlSessionFactory 接口的实现类

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
java复制代码package mybatis.sqlsession.defaults;

import mybatis.cfg.Configuration;
import mybatis.sqlsession.SqlSession;
import mybatis.sqlsession.SqlSessionFactory;

/**
* SqlSessionFactory 接口的实现
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {

private Configuration cfg;

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

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

SqlSessionFactory 中有一个 public SqlSession openSession() 方法,需要返回一个SqlSession的对象。

3.6.2 SqlSession 接口的实现类

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
java复制代码package mybatis.sqlsession.defaults;

import mybatis.cfg.Configuration;
import mybatis.sqlsession.SqlSession;
import mybatis.sqlsession.proxy.MapperProxy;
import mybatis.utils.DataSourceUtil;

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

public class DefaultSqlSession implements SqlSession {

private Configuration cfg;
private Connection connection;

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

/**
* 创建代理对象
* @param daoInterfaceClass dao的接口字节码
* @param <T>
* @return
*/
public <T> T getMapper(Class<T> daoInterfaceClass) {
// 使用代理
// 被代理对象的类加载器
// 相同的接口
// 如何代理
return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
new Class[]{daoInterfaceClass},
new MapperProxy(cfg.getMappers(), connection));
}

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

DefaultSqlSession 中使用到的工具类 DataSourceUtil 为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码package mybatis.utils;

import mybatis.cfg.Configuration;

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

/**
* 创建数据源的工具类
*/
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);
}
}
}

该类的 public T getMapper(Class daoInterfaceClass) 方法通过代理模式来实现

1
2
3
java复制代码public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

loader: 用哪个类加载器去加载代理对象

interfaces:动态代理类需要实现的接口

h:动态代理方法在执行时,会调用h里面的invoke方法去执行

需要定义一个类 MapperProxy 来作为上面的h,该类必须实现 InvocationHandler 接口:

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
java复制代码package mybatis.sqlsession.proxy;

import mybatis.cfg.Mapper;
import mybatis.utils.Executor;

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

public class MapperProxy implements InvocationHandler {

// key : 全限定类名 + 方法名
private Map<String, Mapper> mappers;
private Connection conn;

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

// 对方法进行增强,其实就是调用selectList方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 获取方法所在类的名称
String className = method.getDeclaringClass().getName();

String key = className + "." + methodName;
Mapper mapper = mappers.get(key);
if (mapper == null){
throw new IllegalArgumentException("传入的参数有误");
}

// 调用工具类查询所有
return new Executor().selectList(mapper, conn);
}
}

MapperProxy 中的使用到的工具类 Execute 为(中间定义了selectList方法):

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
java复制代码package mybatis.utils;

import 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;

/**
* 负责执行SQL语句,并且封装结果集
*/
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();
}
}
}
}

3.7 基于注解的查询所有

修改 resources.SqlMapConfig.xml 中的语法为注解相应的形式:

1
2
3
xml复制代码<mappers>
<mapper class="dao.UserDao" />
</mappers>

dao.UserDao 添加注解:

1
2
java复制代码@Select("select * from user")
List<User> findAll();

新建一个 mybatis.annotations.Select 注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码package mybatis.annotations;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
/**
* 配置sql语句
*/
String value();
}

utils.XMLConfigBuilder 中添加根据传入的参数,得到dao中所有被select注解标注的方法:

基于反射的原理,传入一个全限定类名,再通过这个类名获取字节码对象、方法,将带有注解的方法的相关信息加入到Mapper对象中。

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
java复制代码/**
* 根据传入的参数,得到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;
}

此时运行test程序即可看到查询数据库的结果。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%