目录
定义
需求背景
方案设计
代码展示
UML图
实现细节
测试验证
总结
源码地址(已开源):https://gitee.com/sizhaohe/mini-mybatis.git 跟着源码及下述UML图来理解上手会更快,拒绝浮躁,沉下心来搞
定义:
ORM:Object Relational Mapping --> 对象关系映射,是一种程序设计技术,用于实现面向对象编程语言里面不同类型系统的数据之间的转换
需求背景:
记不记得刚开始学JAVA时,编写一大串JDBC相关代码来进行与数据库的交互,日后我们接触到的MyBatis、MyBatisPlus等都是使用ORM组件来实现的框架。
本篇文章提炼出mybatis【最】经典、【最】精简、【最】核心的代码设计,来实现一个【mini-mybatis】,从而熟悉并掌握ORM框架的涉及实现。
方案设计:
- 中间的四部分处理是ORM框架的核心内容
- 这个框架会提供出SqlSession工厂以及调用方式
代码展示
UML图
很重要,建议code前跟我一样,先将类UML图整理出来,整个类的依赖关系及代码执行流程会一目而然。
篇幅有限,展开观看
- 以上为ORM框架实现核心类:加载mysql配置文件、对mapper-xml解析、获取数据库session、操作数据库及封装响应结果。
实现细节
1.定义sqlsession接口
对数据库的定义和处理,本篇我们只封装一个 T selectOne(Object param);
public interface SqlSession {
<T> T selectOne(String statement, Object parameter);
void close();
}
2.DefaultSqlSession(SqlSession的实现)
使用rt.jar包下(java.lang.sql包下)
Connection接口(负责与数据库进行连接)及PreparedStatement(执行具体sql)接口来实现
public class DefaultSqlSession implements SqlSession{
private Connection connection;
private Map<String,XNode> mapperElement;
public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
this.connection = connection;
this.mapperElement = mapperElement;
}
@Override
public <T> T selectOne(String statement, Object parameter) {
XNode xNode = mapperElement.get(statement);
Map<Integer, String> parameterMap = xNode.getParameter();
try {
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
buildParameter(preparedStatement, parameter, parameterMap);
// SQL执行结果集的行数据
ResultSet resultSet = preparedStatement.executeQuery();
List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
return objects.get(0);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
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);
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());
}
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();
}
}
private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {
int size = parameterMap.size();
// 单个参数
if (parameter instanceof Long) {
for (int i = 1; i <= size; i++) {
preparedStatement.setLong(i, Long.parseLong(parameter.toString()));
}
return;
}else{
// TODO 后面紧跟的章节继续补充其他类型的入参
}
}
}
3.定义SqlSessionFactory接口
每次执行一个SQL语句,应用程序都需要获取一个SqlSession对象。SqlSession对象是执行持久化操作的入口点,可以用于执行SQL语句、刷新缓存、提交事务等操作。建议在使用完SqlSession后,及时关闭它来释放资源。
public interface SqlSessionFactory {
SqlSession openSession();
}
4.DefaultSqlSessionFactory(上述接口实现类)
构造方法中向下传递了Configuration配置文件
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration.getConnection(), configuration.getMapperElement());
}
}
5.SqlSessionFactoryBuilder
数据库操作的核心类,负责解析Mapper文件(拿datasource,数据库连接信息,mapper文件中sql的各个信息如id,入返参类型,sql)
public class SqlSessionFactoryBuilder {
public DefaultSqlSessionFactory build(Reader reader) {
SAXReader saxReader = new SAXReader();
Document document = null;
try {
document = saxReader.read(new InputSource(reader));
// 拿到根标签元素
Element rootElement = document.getRootElement();
Configuration configuration = parseConfiguration(rootElement);
return new DefaultSqlSessionFactory(configuration);
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
public Configuration parseConfiguration(Element rootElement) {
Configuration configuration = new Configuration();
configuration.setDataSource(dataSource(rootElement.selectNodes("//dataSource")));
configuration.setConnection(connection(configuration.getDataSource()));
configuration.setMapperElement(mapperElement(rootElement.selectNodes("//mappers")));
return configuration;
}
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;
}
private Connection connection(Map<String, String> dataSource) {
try {
return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
private Map<String, XNode> mapperElement(List<Element> list) {
Map<String, XNode> map = new HashMap<>();
Element element = list.get(0);
List content = element.content();
try {
for (Object o : content) {
Element e = (Element) o;
// 拿到mapper文件对应地址
String resource = e.attributeValue("resource");
Reader reader = Resources.getResourceAsReader(resource);
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new InputSource(reader));
Element rootElement = document.getRootElement();
String namespace = rootElement.attributeValue("namespace");
List<Element> selectNodes = rootElement.selectNodes("select");
for (Element ele : selectNodes) {
String id = ele.attributeValue("id");
String parameterType = ele.attributeValue("parameterType");
String resultType = ele.attributeValue("resultType");
String sql = ele.getText();
// ? 匹配
Map<Integer, String> parameter = new HashMap<>();
Pattern pattern = Pattern.compile("(#\\{(.*?)})");
Matcher matcher = pattern.matcher(sql);
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 = new XNode();
xNode.setId(id);
xNode.setNameSpace(namespace);
xNode.setParameterType(parameterType);
xNode.setResultType(resultType);
xNode.setSql(sql);
xNode.setParameter(parameter);
map.put(namespace + "." + id, xNode);
}
}
}catch (Exception e){
e.printStackTrace();
}
return map;
}
}
测试验证
建表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT '自增id',
`userId` varchar(9) DEFAULT NULL COMMENT '用户ID',
`userNickName` varchar(32) DEFAULT NULL COMMENT '用户昵称',
`userHead` varchar(255) DEFAULT NULL COMMENT '用户头像',
`userPassword` varchar(255) DEFAULT NULL COMMENT '用户密码',
`createTime` datetime DEFAULT NULL COMMENT '创建时间',
`updateTime` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, '001', 'xxx', '001', '123', '2023-07-14 17:33:55', '2023-07-14 17:33:58');
INSERT INTO `user` VALUES (2, '002', 'xxx2', '002', '123', '2023-07-14 17:33:55', '2023-07-14 17:33:58');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
定义POJO及DAO
@Data
public class User {
private Long id;
private String userId; // 用户ID
private String userNickName; // 昵称
private String userHead; // 头像
private String userPassword; // 密码
private Date createTime; // 创建时间
private Date updateTime; // 更新时间
}
public interface IUserDao {
User queryUserInfoById(Long id);
}
ORM配置文件--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.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://172.17.1.245:3306/airticketbasedb?useUnicode=true"/>
<property name="username" value="write"/>
<property name="password" value="write123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/User_Mapper.xml"/>
</mappers>
</configuration>
Mapper配置
UserMapper.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="com.example.minimybatis.dao.IUserDao">
<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.example.minimybatis.po.User">
SELECT id, userId, userNickName, userHead, userPassword, createTime
FROM user
where id = #{id}
</select>
</mapper>
测试类
public class ApiTest {
@Test
public void test(){
String resouce = "mybatis-config-datasource.xml";
Reader reader;
try{
reader = Resources.getResourceAsReader(resouce);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne(
"com.example.minimybatis.dao.IUserDao.queryUserInfoById",
1L);
System.out.println(JSONObject.toJSONString(user));
}catch (Exception e){
e.printStackTrace();
}
}
}
总结
比mybatis小很多,取其(mybaits)精华来达到掌握ORM框架的目的