0


SpringBoot中间件—ORM(Mybatis)框架实现

源码地址(已开源)https://gitee.com/sizhaohe/mini-mybatis.git 跟着源码及下述UML图来理解上手会更快,拒绝浮躁,沉下心来搞

定义:

  1. ORMObject Relational Mapping --> 对象关系映射,是一种程序设计技术,用于实现面向对象编程语言里面不同类型系统的数据之间的转换

需求背景:

  1. 记不记得刚开始学JAVA时,编写一大串JDBC相关代码来进行与数据库的交互,日后我们接触到的MyBatisMyBatisPlus等都是使用ORM组件来实现的框架。
  2. 本篇文章提炼出mybatis【最】经典、【最】精简、【最】核心的代码设计,来实现一个【mini-mybatis】,从而**熟悉并掌握ORM框架的涉及实现。**

方案设计:

  1. ![](https://img-blog.csdnimg.cn/0b815b8e2a094a09b54c21fa2264e688.png)
  • 中间的四部分处理是ORM框架的核心内容
  • 这个框架会提供出SqlSession工厂以及调用方式

代码展示

UML图

很重要,建议code前跟我一样,先将类UML图整理出来,整个类的依赖关系及代码执行流程会一目而然。

篇幅有限,展开观看

  • 以上为ORM框架实现核心类:加载mysql配置文件、对mapper-xml解析、获取数据库session、操作数据库及封装响应结果。

实现细节

1.定义sqlsession接口

对数据库的定义和处理,本篇我们只封装一个 T selectOne(Object param);

  1. public interface SqlSession {
  2. <T> T selectOne(String statement, Object parameter);
  3. void close();
  4. }

2.DefaultSqlSession(SqlSession的实现)

使用rt.jar包下(java.lang.sql包下)

Connection接口(负责与数据库进行连接)及PreparedStatement(执行具体sql)接口来实现

  1. public class DefaultSqlSession implements SqlSession{
  2. private Connection connection;
  3. private Map<String,XNode> mapperElement;
  4. public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
  5. this.connection = connection;
  6. this.mapperElement = mapperElement;
  7. }
  8. @Override
  9. public <T> T selectOne(String statement, Object parameter) {
  10. XNode xNode = mapperElement.get(statement);
  11. Map<Integer, String> parameterMap = xNode.getParameter();
  12. try {
  13. PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
  14. buildParameter(preparedStatement, parameter, parameterMap);
  15. // SQL执行结果集的行数据
  16. ResultSet resultSet = preparedStatement.executeQuery();
  17. List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
  18. return objects.get(0);
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. return null;
  23. }
  24. private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
  25. List<T> list = new ArrayList<>();
  26. try {
  27. ResultSetMetaData metaData = resultSet.getMetaData();
  28. int columnCount = metaData.getColumnCount();
  29. // 每次遍历行值
  30. while (resultSet.next()) {
  31. T obj = (T) clazz.newInstance();
  32. for (int i = 1; i <= columnCount; i++) {
  33. Object value = resultSet.getObject(i);
  34. String columnName = metaData.getColumnName(i);
  35. String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
  36. Method method;
  37. if (value instanceof Timestamp) {
  38. method = clazz.getMethod(setMethod, Date.class);
  39. } else {
  40. method = clazz.getMethod(setMethod, value.getClass());
  41. }
  42. method.invoke(obj, value);
  43. }
  44. list.add(obj);
  45. }
  46. } catch (Exception e) {
  47. e.printStackTrace();
  48. }
  49. return list;
  50. }
  51. @Override
  52. public void close() {
  53. if (null == connection) return;
  54. try {
  55. connection.close();
  56. } catch (SQLException e) {
  57. e.printStackTrace();
  58. }
  59. }
  60. private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {
  61. int size = parameterMap.size();
  62. // 单个参数
  63. if (parameter instanceof Long) {
  64. for (int i = 1; i <= size; i++) {
  65. preparedStatement.setLong(i, Long.parseLong(parameter.toString()));
  66. }
  67. return;
  68. }else{
  69. // TODO 后面紧跟的章节继续补充其他类型的入参
  70. }
  71. }
  72. }

3.定义SqlSessionFactory接口

  1. 每次执行一个SQL语句,应用程序都需要获取一个SqlSession对象。SqlSession对象是执行持久化操作的入口点,可以用于执行SQL语句、刷新缓存、提交事务等操作。建议在使用完SqlSession后,及时关闭它来释放资源。
  1. public interface SqlSessionFactory {
  2. SqlSession openSession();
  3. }

4.DefaultSqlSessionFactory(上述接口实现类)

  1. 构造方法中向下传递了Configuration配置文件
  1. public class DefaultSqlSessionFactory implements SqlSessionFactory {
  2. private final Configuration configuration;
  3. public DefaultSqlSessionFactory(Configuration configuration) {
  4. this.configuration = configuration;
  5. }
  6. @Override
  7. public SqlSession openSession() {
  8. return new DefaultSqlSession(configuration.getConnection(), configuration.getMapperElement());
  9. }
  10. }

5.SqlSessionFactoryBuilder

  1. 数据库操作的核心类,负责解析Mapper文件(拿datasource,数据库连接信息,mapper文件中sql的各个信息如id,入返参类型,sql
  1. public class SqlSessionFactoryBuilder {
  2. public DefaultSqlSessionFactory build(Reader reader) {
  3. SAXReader saxReader = new SAXReader();
  4. Document document = null;
  5. try {
  6. document = saxReader.read(new InputSource(reader));
  7. // 拿到根标签元素
  8. Element rootElement = document.getRootElement();
  9. Configuration configuration = parseConfiguration(rootElement);
  10. return new DefaultSqlSessionFactory(configuration);
  11. } catch (DocumentException e) {
  12. e.printStackTrace();
  13. }
  14. return null;
  15. }
  16. public Configuration parseConfiguration(Element rootElement) {
  17. Configuration configuration = new Configuration();
  18. configuration.setDataSource(dataSource(rootElement.selectNodes("//dataSource")));
  19. configuration.setConnection(connection(configuration.getDataSource()));
  20. configuration.setMapperElement(mapperElement(rootElement.selectNodes("//mappers")));
  21. return configuration;
  22. }
  23. private Map<String, String> dataSource(List<Element> list) {
  24. Map<String, String> dataSource = new HashMap<>(4);
  25. Element element = list.get(0);
  26. List content = element.content();
  27. for (Object o : content) {
  28. Element e = (Element) o;
  29. String name = e.attributeValue("name");
  30. String value = e.attributeValue("value");
  31. dataSource.put(name, value);
  32. }
  33. return dataSource;
  34. }
  35. private Connection connection(Map<String, String> dataSource) {
  36. try {
  37. return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
  38. } catch (SQLException e) {
  39. e.printStackTrace();
  40. }
  41. return null;
  42. }
  43. private Map<String, XNode> mapperElement(List<Element> list) {
  44. Map<String, XNode> map = new HashMap<>();
  45. Element element = list.get(0);
  46. List content = element.content();
  47. try {
  48. for (Object o : content) {
  49. Element e = (Element) o;
  50. // 拿到mapper文件对应地址
  51. String resource = e.attributeValue("resource");
  52. Reader reader = Resources.getResourceAsReader(resource);
  53. SAXReader saxReader = new SAXReader();
  54. Document document = saxReader.read(new InputSource(reader));
  55. Element rootElement = document.getRootElement();
  56. String namespace = rootElement.attributeValue("namespace");
  57. List<Element> selectNodes = rootElement.selectNodes("select");
  58. for (Element ele : selectNodes) {
  59. String id = ele.attributeValue("id");
  60. String parameterType = ele.attributeValue("parameterType");
  61. String resultType = ele.attributeValue("resultType");
  62. String sql = ele.getText();
  63. // ? 匹配
  64. Map<Integer, String> parameter = new HashMap<>();
  65. Pattern pattern = Pattern.compile("(#\\{(.*?)})");
  66. Matcher matcher = pattern.matcher(sql);
  67. for (int i = 1; matcher.find(); i++) {
  68. String g1 = matcher.group(1);
  69. String g2 = matcher.group(2);
  70. parameter.put(i, g2);
  71. sql = sql.replace(g1, "?");
  72. }
  73. XNode xNode = new XNode();
  74. xNode.setId(id);
  75. xNode.setNameSpace(namespace);
  76. xNode.setParameterType(parameterType);
  77. xNode.setResultType(resultType);
  78. xNode.setSql(sql);
  79. xNode.setParameter(parameter);
  80. map.put(namespace + "." + id, xNode);
  81. }
  82. }
  83. }catch (Exception e){
  84. e.printStackTrace();
  85. }
  86. return map;
  87. }
  88. }

测试验证

建表

  1. DROP TABLE IF EXISTS `user`;
  2. CREATE TABLE `user` (
  3. `id` bigint(20) NOT NULL COMMENT '自增id',
  4. `userId` varchar(9) DEFAULT NULL COMMENT '用户ID',
  5. `userNickName` varchar(32) DEFAULT NULL COMMENT '用户昵称',
  6. `userHead` varchar(255) DEFAULT NULL COMMENT '用户头像',
  7. `userPassword` varchar(255) DEFAULT NULL COMMENT '用户密码',
  8. `createTime` datetime DEFAULT NULL COMMENT '创建时间',
  9. `updateTime` datetime NOT NULL COMMENT '更新时间',
  10. PRIMARY KEY (`id`)
  11. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  12. -- ----------------------------
  13. -- Records of user
  14. -- ----------------------------
  15. BEGIN;
  16. INSERT INTO `user` VALUES (1, '001', 'xxx', '001', '123', '2023-07-14 17:33:55', '2023-07-14 17:33:58');
  17. INSERT INTO `user` VALUES (2, '002', 'xxx2', '002', '123', '2023-07-14 17:33:55', '2023-07-14 17:33:58');
  18. COMMIT;
  19. SET FOREIGN_KEY_CHECKS = 1;

定义POJO及DAO

  1. @Data
  2. public class User {
  3. private Long id;
  4. private String userId; // 用户ID
  5. private String userNickName; // 昵称
  6. private String userHead; // 头像
  7. private String userPassword; // 密码
  8. private Date createTime; // 创建时间
  9. private Date updateTime; // 更新时间
  10. }
  1. public interface IUserDao {
  2. User queryUserInfoById(Long id);
  3. }

ORM配置文件--mybatis-config-datasource.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  3. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  4. <configuration>
  5. <environments default="development">
  6. <environment id="development">
  7. <transactionManager type="JDBC"/>
  8. <dataSource type="POOLED">
  9. <property name="driver" value="com.mysql.jdbc.Driver"/>
  10. <property name="url" value="jdbc:mysql://172.17.1.245:3306/airticketbasedb?useUnicode=true"/>
  11. <property name="username" value="write"/>
  12. <property name="password" value="write123"/>
  13. </dataSource>
  14. </environment>
  15. </environments>
  16. <mappers>
  17. <mapper resource="mapper/User_Mapper.xml"/>
  18. </mappers>
  19. </configuration>

Mapper配置

UserMapper.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.example.minimybatis.dao.IUserDao">
  4. <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.example.minimybatis.po.User">
  5. SELECT id, userId, userNickName, userHead, userPassword, createTime
  6. FROM user
  7. where id = #{id}
  8. </select>
  9. </mapper>

测试类

  1. public class ApiTest {
  2. @Test
  3. public void test(){
  4. String resouce = "mybatis-config-datasource.xml";
  5. Reader reader;
  6. try{
  7. reader = Resources.getResourceAsReader(resouce);
  8. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
  9. SqlSession sqlSession = sqlSessionFactory.openSession();
  10. User user = sqlSession.selectOne(
  11. "com.example.minimybatis.dao.IUserDao.queryUserInfoById",
  12. 1L);
  13. System.out.println(JSONObject.toJSONString(user));
  14. }catch (Exception e){
  15. e.printStackTrace();
  16. }
  17. }
  18. }

总结

比mybatis小很多,取其(mybaits)精华来达到掌握ORM框架的目的


本文转载自: https://blog.csdn.net/weixin_44758548/article/details/131754571
版权归原作者 看表该更新博客了 所有, 如有侵权,请联系我们删除。

“SpringBoot中间件—ORM(Mybatis)框架实现”的评论:

还没有评论