Mybatis学习(2)开发环境准备 自定义mybatis

一、开发环境准备

用户表的sql语句:
CREATE TABLE `user` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL COMMENT '用户姓名',
`sex` char(1) DEFAULT NULL COMMENT '性别',
`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
1
2
3
4
5
6
7
项目结构图:


通过Mybatis 查询数据库信息主要有以下几步,自定义Mybatis 也是从以下着手。

读取配置文件
创建SqlSessionFactory工厂(使用构建者模式,把对象的创建信息隐藏,使得调用方法的时候就可拿到对象)
使用工厂创建SqlSession对象(使用工厂模式,降低类之间的依赖关系)
.使用SqlSession对象与数据库交互,创建dao接口的代理对象(使用代理模式,不修改源码的基础上对方法进行增强)
使用代理对象执行dao接口中的方法
1.创建maven工程

2.引入需要的坐标

pom.xml

<?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>com.zxy</groupId>
<artifactId>mybatis_design</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!-- 导入Mysql数据库链接jar包 -->
<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>
<!-- 引入dom4j,用来解析xml-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- 引入jaxen,用来转换Java字节代码或XML的Java bean为xml-->
<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
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
二、在项目中引入工具类

1. XMLConfigBuilder 类

用于解析配置文件 , 主要是通过反射将属性值保存到map中

扫描二维码关注公众号,回复: 8710831 查看本文章

public class XMLConfigBuilder {

/**
* 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方
*
*/
public static Configuration loadConfiguration(InputStream config) {
try {

http://www.architbang.com/37536
http://www.djhaiba.com/index.php/26052/home


// 定义封装连接信息的配置对象(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/zxy/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();
// 判断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;
}
}

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
2. Executor 类

负责执行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();
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();
}
}
}
}

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
三、 编写 Mybatis-configs.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- mybatis的主配置文件 -->
<configuration>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置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/demo?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<!-- 使用xml-->
<mapper resource="com/zxy/dao/IUserDao.xml"/>
<!-- 使用注解 -->
<!-- <mapper class="com.zxy.dao.IUserDao"/>-->
</mappers>
</configuration>
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
四、自定义mybatis

1. 读取配置文件的类

Resources : 使用类加载器读取配置文件

public class Resources {
/**
* 根据传入的参数获取字节输入流
*
* @param filePath
* @return
*/
public static InputStream getResourceAsStream(String filePath) {
// 获取当前类的字节码,获取字节码的类加载器,根据类加载器读取配置文件信息
return Resources.class.getClassLoader().getResourceAsStream(filePath);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
2. Mapper类

用来封装执行的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;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
3. mybatis的配置类

存放连接数据库的信息以及执行的语句和封装的结果集


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 Map<String, Mapper> getMappers() {
return mappers;
}

public void setMappers(Map<String, Mapper> mappers) {
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;
}
}

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
4. User实体类

public class User implements Serializable {
private Integer id;
private String username;
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 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 + '\'' +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}

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
5. 持久层dao接口和 IUserDao.xml

用户持久层接口

public interface IUserDao {
/**
* 查询所有信息
*
* @return
*/
// @Select("select * from user")
List<User> findAll();
}

1
2
3
4
5
6
7
8
9
10
IUserDao.xml

<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.zxy.dao.IUserDao">
<!-- 配置查询所有信息-->
<select id="findAll" resultType="com.zxy.pojo.User">
select * from user
</select>
</mapper>
1
2
3
4
5
6
7
6. 构建者类

SqlSessionFactoryBuilder : 用于创建一个SqlSessionFactory对象

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

1
2
3
4
5
6
7
8
9
10
11
12
13
7. SqlSessionFactory接口和实现类

SqlSessionFactory接口 :用于创建一个新的SqlSession对象


public interface SqlSessionFactory {

SqlSession openSession();
}

1
2
3
4
5
6
SqlSessionFactory 接口的实现类

public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration cfg;

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

/**
* 用于创建一个新的连接
*
* @return
*/
public SqlSession openSession() {
return new DefaultSqlSession(cfg);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
8. SqlSession接口和实现类

SqlSession接口 : 自定义mybatis中和数据库交互的核心类,它里面可以创建dao接口的代理对象

package com.zxy.mybatis.sqlsession;

public interface SqlSession {
/**
* 根据参数创建一个代理对象
*
* @param daoInterfaceClass dao接口的字节码
* @param <T>
* @return
*/
<T> T getMapper(Class<T> daoInterfaceClass);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SqlSession 接口的实现类

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 (SQLException e) {
e.printStackTrace();
}
}
}
}

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
9. Dao接口代理对象的类

public class MapperProxy implements InvocationHandler {

// map 的key是全限定类名加方法名
private Map<String, Mapper> mappers;

private Connection conn;

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

/**
* 用于对方法增强,调用select1List方法
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
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, conn);
}
}

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
10. DataSourceUtil 类

用于创建数据源

public class DataSourceUtil {
/**
* 用来获取连接
*
* @param cfg
* @return
*/
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);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
11. 测试类

public class MybatisTest {
public static void main(String[] args) throws Exception {
// 1.读取配置文件
InputStream input = Resources.getResourceAsStream("Mybatis-configs.xml");
// 2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 使用构建者模式,把对象的创建信息隐藏,使得调用方法的时候就可拿到对象
SqlSessionFactory factory = builder.build(input);
// 3.使用工厂创建SqlSession对象(借助此对象与数据库交互)
// 使用工厂模式,解耦(降低类之间的依赖关系)
SqlSession session = factory.openSession();
// 4.使用SqlSession对象创建dao接口的代理对象
// 使用代理模式,不修改源码的基础上对方法进行增强
IUserDao userDao = session.getMapper(IUserDao.class);
// 5.使用代理对象执行dao接口中的方法
List<User> list = userDao.findAll();
for (User user : list) {
System.out.println(user);
}
// 6.释放资源
session.close();
input.close();
}
}

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
使用的是XML进行解析
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'
User{id=1, username='架构师', sex='男', address='西安市高新区'}
User{id=2, username='产品经理', sex='男', address='西安市长安区'}
User{id=3, username='程序员', sex='女', address='西安市雁塔区'}
————————————————
版权声明:本文为CSDN博主「扬帆向海」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43570367/article/details/103244430

猜你喜欢

转载自www.cnblogs.com/dasdfdfecvcx/p/12214472.html
今日推荐