目录
定义
需求背景
方案设计
代码展示
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;}@Overridepublic <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;}@Overridepublic 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;}@Overridepublic 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; // 用户IDprivate 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, createTimeFROM userwhere id = #{id}</select></mapper>
测试类
public class ApiTest {@Testpublic 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框架的目的