SpringBoot中间件ORM框架实现案例详解(Mybatis)
作者:看表该更新博客了
源码地址(已开源):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框架的目的
到此这篇关于SpringBoot中间件ORM框架实现案例详解(Mybatis)的文章就介绍到这了,更多相关SpringBoot中间件ORM内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!