SpringBoot中間件ORM框架實現(xiàn)案例詳解(Mybatis)
源碼地址(已開源):https://gitee.com/sizhaohe/mini-mybatis.git 跟著源碼及下述UML圖來理解上手會更快,拒絕浮躁,沉下心來搞
定義:
ORM:Object Relational Mapping --> 對象關系映射,是一種程序設計技術,用于實現(xiàn)面向對象編程語言里面不同類型系統(tǒng)的數(shù)據(jù)之間的轉換
需求背景:
記不記得剛開始學JAVA時,編寫一大串JDBC相關代碼來進行與數(shù)據(jù)庫的交互,日后我們接觸到的MyBatis、MyBatisPlus等都是使用ORM組件來實現(xiàn)的框架。
本篇文章提煉出mybatis【最】經(jīng)典、【最】精簡、【最】核心的代碼設計,來實現(xiàn)一個【mini-mybatis】,從而熟悉并掌握ORM框架的涉及實現(xiàn)。
方案設計:
- 中間的四部分處理是ORM框架的核心內容
- 這個框架會提供出SqlSession工廠以及調用方式
代碼展示
UML圖
很重要,建議code前跟我一樣,先將類UML圖整理出來,整個類的依賴關系及代碼執(zhí)行流程會一目而然。
- 以上為ORM框架實現(xiàn)核心類:加載mysql配置文件、對mapper-xml解析、獲取數(shù)據(jù)庫session、操作數(shù)據(jù)庫及封裝響應結果。
實現(xiàn)細節(jié)
1.定義sqlsession接口
對數(shù)據(jù)庫的定義和處理,本篇我們只封裝一個 T selectOne(Object param);
public interface SqlSession { <T> T selectOne(String statement, Object parameter); void close(); }
2.DefaultSqlSession(SqlSession的實現(xiàn))
使用rt.jar包下(java.lang.sql包下)
Connection接口(負責與數(shù)據(jù)庫進行連接)及PreparedStatement(執(zhí)行具體sql)接口來實現(xiàn)
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執(zhí)行結果集的行數(shù)據(jù) 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(); // 單個參數(shù) if (parameter instanceof Long) { for (int i = 1; i <= size; i++) { preparedStatement.setLong(i, Long.parseLong(parameter.toString())); } return; }else{ // TODO 后面緊跟的章節(jié)繼續(xù)補充其他類型的入?yún)? } } }
3.定義SqlSessionFactory接口
每次執(zhí)行一個SQL語句,應用程序都需要獲取一個SqlSession對象。SqlSession對象是執(zhí)行持久化操作的入口點,可以用于執(zhí)行SQL語句、刷新緩存、提交事務等操作。建議在使用完SqlSession后,及時關閉它來釋放資源。
public interface SqlSessionFactory { SqlSession openSession(); }
4.DefaultSqlSessionFactory(上述接口實現(xiàn)類)
構造方法中向下傳遞了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
數(shù)據(jù)庫操作的核心類,負責解析Mapper文件(拿datasource,數(shù)據(jù)庫連接信息,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("http://dataSource"))); configuration.setConnection(connection(configuration.getDataSource())); configuration.setMapperElement(mapperElement(rootElement.selectNodes("http://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 '創(chuàng)建時間', `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; // 創(chuàng)建時間 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框架實現(xiàn)案例詳解(Mybatis)的文章就介紹到這了,更多相關SpringBoot中間件ORM內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!