整体概述
精简版 MyBatis 手写实现,复刻 MyBatis 核心执行流程,抛弃复杂细节(连接池、动态 SQL、Mapper 代理),把 MyBatis 拆分成三层:
-
SqlSessionFactoryBuilder:建造者,解析 xml 配置文件; -
SqlSessionFactory:工厂,用来创建 SqlSession; -
SqlSession:执行层,封装 JDBC,执行 SQL 并且把查询结果转为 Java 对象。底层还是原生 JDBC,只是做了封装。
我们自上而下逐段解析。
一、整体文件结构
resources
├─ mybatis-config-datasource.xml 全局配置(数据库连接信息、mapper文件路径)
├─ School_Mapper.xml
└─ User_Mapper.xml
java
└─ org.itstack.demo.design
├─ SqlSession.java 会话接口
├─ DefaultSqlSession.java 会话实现类(执行SQL)
├─ SqlSessionFactory.java 工厂接口
├─ DefaultSqlSessionFactory.java
├─ SqlSessionFactoryBuilder.java 解析xml,构建工厂
├─ Configuration.java 全局配置类
└─ XNode.java 封装每一条select标签里面的数据(sql、resultType等)
整体调用链路: SqlSessionFactoryBuilder(读取 xml 配置) → DefaultSqlSessionFactory(工厂) → DefaultSqlSession(执行 JDBC)
辅助类: Configuration(全局配置)、XNode(封装 SQL 信息)。
1.Configuration.java(全局配置类,数据载体)
public class Configuration {
// 数据源配置信息:driver、url、username、password
public Map<String,String> dataSource;
// JDBC数据库连接对象
public Connection connection;
// key=namespace.id,value=解析之后的SQL信息XNode
public Map<String,XNode> mapperElement;
// getter、setter省略
public void setDataSource(Map<String, String> dataSource) {
this.dataSource = dataSource;
}
public void setConnection(Connection connection) {
this.connection = connection;
}
public void setMapperElement(Map<String, XNode> mapperElement) {
this.mapperElement = mapperElement;
}
}
类整体说明
-
该类作用:存放全局解析之后的全部数据,相当于一个数据容器;
-
生命周期:在
SqlSessionFactoryBuilder.build()的时候被创建;之后DefaultSqlSessionFactory全程持有这个对象; -
成员变量:
dataSource:保存 xml 里面数据库的 4 项配置;connection:JDBC 连接;mapperElement:存放所有 mapper 文件里面解析完成的 SQL 语句;
-
后续
DefaultSqlSession创建实例时,就是从 Configuration 取出connection和mapperElement。
方法解析
setDataSource:把解析出来的数据库配置存入 Configuration;setConnection:传入创建好的 JDBC 连接;setMapperElement:存入解析完成的所有 SQL 语句。
2.XNode.java(对应 Mybatis 里 MappedStatement)
public class XNode {
// mapper的namespace
private String namespace;
// select标签中的id
private String id;
// 参数类型
private String parameterType;
// 返回值类型
private String resultType;
// #{...}替换成?之后的SQL语句
private String sql;
// key:参数序号,value:参数名称
private Map<Integer,String> parameter;
// getter和setter省略
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public void setId(String id) {
this.id = id;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public void setSql(String sql) {
this.sql = sql;
}
public void setParameter(Map<Integer, String> parameter) {
this.parameter = parameter;
}
public String getSql() {
return sql;
}
public String getResultType() {
return resultType;
}
}
类整体说明
- 类的作用:每一条
<select>标签对应一个 XNode 实例;把 xml 中 select 标签里面所有信息解析之后封装到这个对象; - 创建时机:
SqlSessionFactoryBuilder.mapperElement()方法里面循环解析<select>标签时 new 出来; - 存放内容:namespace、id、处理后的 sql 语句、resultType、参数映射;
- 后续
DefaultSqlSession执行 SQL 的时候,通过statementId(namespace.id)取出 XNode,拿到 SQL 语句和返回类型。
方法解析
- set 方法:
SqlSessionFactoryBuilder解析 xml 之后赋值; - getSql ():给 DefaultSqlSession 获取预处理之后的 SQL;
- getResultType ():获取返回实体类全限定名,反射创建对象。
3.SqlSessionFactory.java 和DefaultSqlSessionFactory.java(工厂模式)
(1)工厂接口 SqlSessionFactory.java
public interface SqlSessionFactory {
// 创建会话对象
SqlSession openSession();
}
(2)实现类 DefaultSqlSessionFactory.java
public class DefaultSqlSessionFactory implements SqlSessionFactory {
// 持有全局配置对象
private final Configuration configuration;
// 构造方法接收已经解析完毕的Configuration
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
// 把连接和解析完毕的SQL集合传给DefaultSqlSession
return new DefaultSqlSession(configuration.connection, configuration.mapperElement);
}
}
类整体讲解
-
SqlSessionFactory是顶层接口,定义工厂的唯一功能:openSession()打开数据库会话; -
DefaultSqlSessionFactory实现这个接口:- 成员变量
configuration:构造方法传入,工厂全程持有全局配置; openSession():创建DefaultSqlSession实例;
- 成员变量
-
职责:工厂只负责生产 SqlSession 对象;
-
调用时机:开发者拿到
DefaultSqlSessionFactory之后,每次做数据库查询调用openSession(); -
注意:这里简化版代码每次 openSession 复用同一个 Connection,生产环境 Mybatis 会从连接池获取连接。
方法解析
-
构造方法
DefaultSqlSessionFactory(Configuration configuration):接收
SqlSessionFactoryBuilder创建好的配置对象;工厂后续创建 SqlSession 全部依赖这个配置; -
openSession():从 configuration 取出 connection 和 mapperElement,实例化 DefaultSqlSession 并且返回给调用者。
4.SqlSession 接口 + DefaultSqlSession 实现类(门面模式,核心执行层)
(1)SqlSession.java 接口
public interface SqlSession {
// 查询单个结果,不带参数
<T> T selectOne(String statement);
// 查询单个结果,携带参数
<T> T selectOne(String statement, Object parameter);
// 查询列表,不带参数
<T> List<T> selectList(String statement);
// 查询列表,携带参数
<T> List<T> selectList(String statement, Object parameter);
// 关闭数据库连接
void close();
}
(2)DefaultSqlSession.java 实现类
public class DefaultSqlSession implements SqlSession {
// JDBC连接
private Connection connection;
// 预解析好的SQL语句集合
private Map<String, XNode> mapperElement;
// 构造方法接收连接和解析完成的SQL集合
public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
this.connection = connection;
this.mapperElement = mapperElement;
}
@Override
public <T> T selectOne(String statement) {
try {
// 通过statementId获取XNode(SQL信息)
XNode xNode = mapperElement.get(statement);
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
ResultSet resultSet = preparedStatement.executeQuery();
// 调用方法把ResultSet转为Java对象集合
List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
return objects.get(0);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public <T> List<T> selectList(String statement) {
XNode xNode = mapperElement.get(statement);
try {
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
ResultSet resultSet = preparedStatement.executeQuery();
return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 将ResultSet结果集利用反射转换成Java实体对象列表
private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
List<T> list = new ArrayList<>();
try {
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
// 遍历每一行查询结果
while (resultSet.next()) {
// 反射创建对象实例
T obj = (T) clazz.newInstance();
for (int i = 1; i <= columnCount; i++) {
Object value = resultSet.getObject(i);
String columnName = metaData.getColumnName(i);
// 根据数据库字段拼接setter方法名,例如userName -> setUserName()
String setMethod = "set" + columnName.substring(0,1).toUpperCase() + columnName.substring(1);
Method method;
// 判断日期类型做适配
if (value instanceof Timestamp) {
method = clazz.getMethod(setMethod, Date.class);
} else {
method = clazz.getMethod(setMethod, value.getClass());
}
// 反射执行set方法给对象赋值
method.invoke(obj, value);
}
list.add(obj);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
@Override
public void close() {
if (null == connection) return;
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 带参数的selectOne、selectList方法这里省略
@Override
public <T> T selectOne(String statement, Object parameter) {return null;}
@Override
public <T> List<T> selectList(String statement, Object parameter) {return null;}
}
类整体讲解
-
SqlSession定义了数据库操作对外暴露的全部方法:查询单个、查询列表、关闭连接; -
DefaultSqlSession是真正干活的类,成员变量:connection:JDBC 连接;由 DefaultSqlSessionFactory 传递进来;mapperElement:提前解析完毕的 SQL 语句 Map;
-
设计模式:外观模式(门面模式)
底层的
Connection、PreparedStatement、ResultSet全部被封装在类内部;外部调用者只需要调用selectOne、selectList,完全看不到底层 JDBC 繁杂代码; -
核心功能两件事:
- 执行 SQL 语句;
- 通过反射把数据库返回的
ResultSet自动封装成 Java 对象(ORM 思想);
-
线程问题:
DefaultSqlSession持有 Connection,因此它是线程不安全的,每次查询都新建 SqlSession。
逐个方法详细解析
- 构造方法:
public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement)
接收工厂传递过来的数据库连接和预解析完成的 SQL 语句集合;之后本类所有方法都依赖这两个成员变量。
-
selectOne(String statement):- 参数
statement:就是namespace.id,例如com.youkeda.mapper.UserMapper.selectUser; - 第一步:从
mapperElement取出 XNode 对象,拿到处理完成的 SQL 语句; - 第二步:创建
PreparedStatement执行 SQL 得到ResultSet; - 第三步:调用
resultSet2Obj方法把结果集转为 Java 对象集合; - 返回列表里面第一个元素。
- 参数
-
selectList(String statement):和 selectOne 逻辑几乎一致,只是返回全部列表,不会取第一条。
-
private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz)(重中之重)-
参数 1:数据库返回的结果集;参数 2:要封装的实体类 Class;
-
ResultSetMetaData:获取查询出来的每一列字段名称; -
clazz.newInstance():反射创建 DO 实体对象; -
根据数据库列名拼接 setter 方法:列名
userName拼接成setUserName(); -
method.invoke(obj,value):反射调用 set 方法给对象赋值; -
一行数据库记录对应一个 Java 对象,最后装入 List 返回;
这就是 MyBatis 实现自动把数据库记录转为 Java 对象底层原理。
-
-
close():关闭 JDBC 数据库连接,释放资源。
5.SqlSessionFactoryBuilder.java(建造者模式,整个程序入口)
public class SqlSessionFactoryBuilder {
// 入口方法,接收配置文件Reader,返回SqlSessionFactory工厂
public DefaultSqlSessionFactory build(Reader reader) {
SAXReader saxReader = new SAXReader();
try {
// 设置本地DTD解析器,断网环境也能解析xml文件
saxReader.setEntityResolver(new XMLMapperEntityResolver());
// 读取mybatis‑config‑datasource.xml文档
Document document = saxReader.read(new InputSource(reader));
// 解析xml全部内容,生成Configuration对象
Configuration configuration = parseConfiguration(document.getRootElement());
// 创建并返回工厂对象
return new DefaultSqlSessionFactory(configuration);
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
// 解析xml根节点,组装Configuration
private Configuration parseConfiguration(Element root) {
Configuration configuration = new Configuration();
// 1.解析dataSource标签拿到数据库配置
configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));
// 2.根据配置创建JDBC连接
configuration.setConnection(connection(configuration.dataSource));
// 3.解析所有mapper文件,得到SQL语句集合
configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));
return configuration;
}
// 获取dataSource里面的driver、url、username、password
private Map<String, String> dataSource(List<Element> list) {
Map<String, String> dataSource = new HashMap<>(4);
Element element = list.get(0);
List content = element.content();
for (Object o : content) {
Element e = (Element) o;
String name = e.attributeValue("name");
String value = e.attributeValue("value");
dataSource.put(name, value);
}
return dataSource;
}
// 根据数据库配置创建Connection
private Connection connection(Map<String, String> dataSource) {
try {
Class.forName(dataSource.get("driver"));
return DriverManager.getConnection(dataSource.get("url"),
dataSource.get("username"), dataSource.get("password"));
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null;
}
// 解析每一个Mapper.xml文件,把#{参数}替换成?,封装成XNode存入Map
private Map<String, XNode> mapperElement(List<Element> list) {
Map<String, XNode> map = new HashMap<>();
Element element = list.get(0);
List content = element.content();
for (Object o : content) {
Element e = (Element) o;
String resource = e.attributeValue("resource");
try {
// 读取mapper文件
Reader reader = Resources.getResourceAsReader(resource);
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new InputSource(reader));
Element root = document.getRootElement();
// 获取mapper的namespace
String namespace = root.attributeValue("namespace");
// 获取所有select标签
List<Element> selectNodes = root.selectNodes("select");
for (Element node : selectNodes) {
String id = node.attributeValue("id");
String parameterType = node.attributeValue("parameterType");
String resultType = node.attributeValue("resultType");
String sql = node.getText();
// 使用正则表达式把#{xxx}替换成JDBC的?
Pattern pattern = Pattern.compile("(#\{(.*?)})");
Matcher matcher = pattern.matcher(sql);
Map<Integer, String> parameter = new HashMap<>();
for (int i = 1; matcher.find(); i++) {
String g1 = matcher.group(1);
String g2 = matcher.group(2);
parameter.put(i, g2);
sql = sql.replace(g1, "?");
}
// 封装XNode对象
XNode xNode = new XNode();
xNode.setNamespace(namespace);
xNode.setId(id);
xNode.setParameterType(parameterType);
xNode.setResultType(resultType);
xNode.setSql(sql);
xNode.setParameter(parameter);
// key=namespace.id
map.put(namespace + "." + id, xNode);
}
} catch (IOException | DocumentException e) {
e.printStackTrace();
}
}
return map;
}
}
类整体讲解
-
这个类使用建造者模式,负责整个 Mybatis 框架初始化工作;
-
整个程序入口就是
build()方法;开发者传入配置文件mybatis‑config‑datasource.xml的 Reader; -
本类内部做三件大事:
① 解析全局配置文件,拿到数据库连接信息;
② 解析每一个 Mapper.xml 文件,把
#{}占位符替换为 JDBC 里面的?;将 SQL 语句封装进 XNode 存入 Map;③ 组装 Configuration 对象,创建
DefaultSqlSessionFactory工厂返回; -
职责划分:所有 xml 解析工作全部集中在这一个类里面,后续 Factory 和 SqlSession 只管执行 SQL,不用再解析 xml;解析和运行阶段彻底分开。
逐个方法详细解析
-
build(Reader reader):入口方法-
saxReader.setEntityResolver(new XMLMapperEntityResolver()):mybatis 配置文件头部引用 dtd 约束文件;如果不配置,程序启动会联网下载 dtd;设置之后读取内置本地 dtd 文件,断网环境也可以解析 xml;
-
document = saxReader.read():dom4j 读取 xml 文档; -
parseConfiguration():解析 xml 里面所有标签内容; -
最后 new
DefaultSqlSessionFactory(configuration)返回工厂。
-
-
parseConfiguration(Element root):组装 Configuration依次调用下面三个私有方法:
-
dataSource():获取 driver、url、username、password; -
connection():创建 JDBC 连接; -
mapperElement():解析所有 mapper 文件,处理 SQL 语句;把解析出来的数据全部放到 Configuration 里面。
-
-
dataSource(List<Element> list):遍历
<dataSource>标签里面的子标签,取出 name 和 value,存入 Map 返回。 -
connection(Map<String, String> dataSource):Class.forName(dataSource.get("driver"))加载数据库驱动;DriverManager.getConnection()创建 JDBC 连接对象。
-
mapperElement(List<Element> list)(最核心方法)-
循环读取
<mapper resource="User_Mapper.xml">; -
获取 mapper 标签中的
namespace属性; -
获取
<select>标签里面 id、parameterType、resultType、原生 sql; -
正则表达式匹配
#{里面的参数},把#{userId}替换成?;并且记录参数顺序; -
实例化 XNode 对象保存处理之后的全部信息;
-
namespace+"."+id作为 key,XNode 作为 value 存入 Map;
解析之后 SqlSession 就可以通过 statementId 快速找到对应的 SQL 语句。
-
完整调用流程,串联全部类
// 1.读取配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");
// 2.建造者解析xml,生成SqlSessionFactory
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
DefaultSqlSessionFactory factory = builder.build(reader);
// 3.工厂创建SqlSession会话对象
SqlSession sqlSession = factory.openSession();
// 4.执行数据库查询
User user = sqlSession.selectOne("com.youkeda.mapper.UserMapper.selectUser");
// 5.关闭连接
sqlSession.close();
SqlSessionFactoryBuilder.build():解析 xml 生成 Configuration,创建工厂;factory.openSession():工厂创建DefaultSqlSession;sqlSession.selectOne():内部执行 JDBC,反射封装 Java 对象;close()关闭连接。
用到的设计模式总结
- 建造者模式:
SqlSessionFactoryBuilder负责解析 xml 组装复杂 Configuration 对象; - 工厂模式:
SqlSessionFactory生产 SqlSession 会话实例; - 外观模式:
DefaultSqlSession封装底层 JDBC 复杂 API,对外提供简单查询方法; - 反射机制:
resultSet2Obj()实现 ORM 映射,数据库记录自动转为 Java 对象。
到这我其实是有一个疑问的就是为什么非要经过 Configuration 和 Factory 中转,为什么不直接 new DefaultSqlSession (connection,mapperElement)?
设想的简化写法:
//build里面直接new DefaultSqlSession(connection,mapperElement);返回给调用者
真实代码是:
configuration → DefaultSqlSessionFactory → openSession() → new DefaultSqlSession(connection,mapperElement)
-
职责拆分(最重要):解析阶段和运行阶段分开
-
SqlSessionFactoryBuilder:只做一次性解析 xml 工作,解析完它的生命周期就结束; -
DefaultSqlSessionFactory长期存活,全局单例,全程持有configuration;以后每次调用openSession()创建新的 SqlSession;如果跳过 Factory,直接返回 SqlSession:
-
SqlSession 里面包含 Connection,SqlSession 用完就要关闭;一旦关闭连接就失效;后续再查询没有办法再次创建新连接;
-
Factory 长期存活,每次
openSession()都可以生成全新的 SqlSession;原生 Mybatis 里面 Factory 是全局单例,SqlSession 每次用完销毁。
-
-
后续扩展考虑:
以后扩展可以写
openSession(boolean autoCommit)、可以选择开启事务;工厂统一控制创建会话逻辑; -
配置统一管理:
Configuration里面不光只有 connection 和 mapperElement,后续还可以存放分页配置、日志配置、插件配置;全部放在 Configuration 统一管理,而不是零散传递参数。
一句话大白话:Builder 只干一次性解析工作;Factory 长期存在负责生产会话对象;职责分开,后期更好扩展。
二、接下来可以测试验证一下
1.MySQL 执行建表语句
新建数据库 itstack_demo_design,执行下面 SQL:
CREATE DATABASE IF NOT EXISTS itstack_demo_design;
USE itstack_demo_design;
CREATE TABLE user (
id bigint NOT NULL AUTO_INCREMENT,
name varchar(32),
age int(4),
address varchar(128),
entryTime datetime,
remark varchar(64),
createTime datetime,
updateTime datetime,
status int(4) DEFAULT '0',
dateTime varchar(64),
PRIMARY KEY (id),
INDEX idx_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status, dateTime) values
(1, '水水', 18, '吉林省榆树市黑林镇尹家村5组', '2019-12-22 00:00:00', '无', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 0, '20200309'),
(2, '豆豆', 18, '辽宁省大连市清河湾司马道407路', '2019-12-22 00:00:00', '无', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 1, null),
(3, '花花', 19, '辽宁省大连市清河湾司马道407路', '2019-12-22 00:00:00', '无', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 0, '20200310');
CREATE TABLE school (
id bigint NOT NULL AUTO_INCREMENT,
name varchar(64),
address varchar(256),
createTime datetime,
updateTime datetime,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into school (id, name, address, createTime, updateTime) values
(1,'北京大学', '北京市海淀区颐和园路5号', '2019-10-18 13:35:57', '2019-10-18 13:35:57'),
(2,'南开大学', '中国天津市南开区卫津路94号', '2019-10-18 13:35:57', '2019-10-18 13:35:57');
2.创建 PO 实体类
User.java
public class User {
private Long id;
private String name;
private Integer age;
private Date createTime;
private Date updateTime;
// ... get/set
}
School.java
public class School {
private Long id;
private String name;
private String address;
private Date createTime;
private Date updateTime;
// ... get/set
}
3.Dao 接口(仅用来提供 namespace,简化版 Mybatis 并不会执行接口里面方法)
public interface IUserDao {
User queryUserInfoById(Long id);
}
public interface ISchoolDao {
School querySchoolInfoById(Long id);
}
4.目录配置文件
(1)mybatis‑config‑datasource.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">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/itstack_demo_design?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/User_Mapper.xml"/>
<mapper resource="mapper/School_Mapper.xml"/>
</mappers>
</configuration>
(2)mapper/User_Mapper.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="org.itstack.demo.design.dao.IUserDao">
<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.design.po.User">
SELECT id, name, age, createTime, updateTime FROM user where id = #{id}
</select>
<select id="queryUserList" parameterType="org.itstack.demo.design.po.User" resultType="org.itstack.demo.design.po.User">
SELECT id, name, age, createTime, updateTime FROM user where age = #{age}
</select>
</mapper>
(3)mapper/School_Mapper.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="org.itstack.demo.design.dao.ISchoolDao">
<select id="querySchoolInfoById" resultType="org.itstack.demo.design.po.School">
SELECT id, name, address, createTime, updateTime FROM school where id = #{id}
</select>
</mapper>
5.测试类 ApiTest.java
public class ApiTest {
private static final Logger logger = LoggerFactory.getLogger(ApiTest.class);
// 测试根据id查询单个用户
public static void testQueryUserById() {
String resource = "mybatis-config-datasource.xml";
Reader reader;
try {
// 1.读取配置文件
reader = Resources.getResourceAsReader(resource);
// 2.建造者解析xml,生成工厂(启动阶段:只执行这一次解析xml)
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(reader);
// 3.工厂创建会话
SqlSession sqlSession = factory.openSession();
try {
// statementId = namespace.id
User user = sqlSession.selectOne("org.itstack.demo.design.dao.IUserDao.queryUserInfoById");
logger.info("查询单个用户结果:{}", JSON.toJSONString(user));
} finally {
// 关闭连接释放资源
sqlSession.close();
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 查询学校信息
public static void testQuerySchoolById() {
String resource = "mybatis-config-datasource.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(reader);
SqlSession sqlSession = factory.openSession();
try {
School school = sqlSession.selectOne("org.itstack.demo.design.dao.ISchoolDao.querySchoolInfoById");
logger.info("查询学校结果:{}", JSON.toJSONString(school));
} finally {
sqlSession.close();
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
testQueryUserById();
testQuerySchoolById();
}
}
6.运行之后输出结果示例
INFO org.itstack.demo.design.ApiTest - 查询单个用户结果:{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000}
INFO org.itstack.demo.design.ApiTest - 查询学校结果:{"address":"北京市海淀区颐和园路5号","createTime":1571387757000,"id":1,"name":"北京大学","updateTime":1571387757000}
现在全部完成了后面感觉可以
- 完善
selectOne(String statement,Object parameter)方法:实现给?赋值,对应 Mybatis 的ParameterHandler; - 实现 JDK 动态代理,摆脱手写字符串
statementId,可以直接调用iUserDao.queryUserInfoById(1L); - 引入 HikariCP 连接池替换现在唯一的 Connection;
- 实现事务功能;
- 实现 Mybatis 拦截器插件。