SpringBoot中間件ORM框架實(shí)現(xiàn)案例詳解(Mybatis)
源碼地址(已開源):https://gitee.com/sizhaohe/mini-mybatis.git 跟著源碼及下述UML圖來(lái)理解上手會(huì)更快,拒絕浮躁,沉下心來(lái)搞
定義:
ORM:Object Relational Mapping --> 對(duì)象關(guān)系映射,是一種程序設(shè)計(jì)技術(shù),用于實(shí)現(xiàn)面向?qū)ο缶幊陶Z(yǔ)言里面不同類型系統(tǒng)的數(shù)據(jù)之間的轉(zhuǎn)換
需求背景:
記不記得剛開始學(xué)JAVA時(shí),編寫一大串JDBC相關(guān)代碼來(lái)進(jìn)行與數(shù)據(jù)庫(kù)的交互,日后我們接觸到的MyBatis、MyBatisPlus等都是使用ORM組件來(lái)實(shí)現(xiàn)的框架。
本篇文章提煉出mybatis【最】經(jīng)典、【最】精簡(jiǎn)、【最】核心的代碼設(shè)計(jì),來(lái)實(shí)現(xiàn)一個(gè)【mini-mybatis】,從而熟悉并掌握ORM框架的涉及實(shí)現(xiàn)。
方案設(shè)計(jì):

- 中間的四部分處理是ORM框架的核心內(nèi)容
- 這個(gè)框架會(huì)提供出SqlSession工廠以及調(diào)用方式
代碼展示
UML圖
很重要,建議code前跟我一樣,先將類UML圖整理出來(lái),整個(gè)類的依賴關(guān)系及代碼執(zhí)行流程會(huì)一目而然。

- 以上為ORM框架實(shí)現(xiàn)核心類:加載mysql配置文件、對(duì)mapper-xml解析、獲取數(shù)據(jù)庫(kù)session、操作數(shù)據(jù)庫(kù)及封裝響應(yīng)結(jié)果。
實(shí)現(xiàn)細(xì)節(jié)
1.定義sqlsession接口
對(duì)數(shù)據(jù)庫(kù)的定義和處理,本篇我們只封裝一個(gè) T selectOne(Object param);
public interface SqlSession {
<T> T selectOne(String statement, Object parameter);
void close();
}2.DefaultSqlSession(SqlSession的實(shí)現(xiàn))
使用rt.jar包下(java.lang.sql包下)
Connection接口(負(fù)責(zé)與數(shù)據(jù)庫(kù)進(jìn)行連接)及PreparedStatement(執(zhí)行具體sql)接口來(lái)實(shí)現(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í)行結(jié)果集的行數(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();
// 單個(gè)參數(shù)
if (parameter instanceof Long) {
for (int i = 1; i <= size; i++) {
preparedStatement.setLong(i, Long.parseLong(parameter.toString()));
}
return;
}else{
// TODO 后面緊跟的章節(jié)繼續(xù)補(bǔ)充其他類型的入?yún)?
}
}
}3.定義SqlSessionFactory接口
每次執(zhí)行一個(gè)SQL語(yǔ)句,應(yīng)用程序都需要獲取一個(gè)SqlSession對(duì)象。SqlSession對(duì)象是執(zhí)行持久化操作的入口點(diǎn),可以用于執(zhí)行SQL語(yǔ)句、刷新緩存、提交事務(wù)等操作。建議在使用完SqlSession后,及時(shí)關(guān)閉它來(lái)釋放資源。
public interface SqlSessionFactory {
SqlSession openSession();
}4.DefaultSqlSessionFactory(上述接口實(shí)現(xiàn)類)
構(gòu)造方法中向下傳遞了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ù)庫(kù)操作的核心類,負(fù)責(zé)解析Mapper文件(拿datasource,數(shù)據(jù)庫(kù)連接信息,mapper文件中sql的各個(gè)信息如id,入返參類型,sql)
public class SqlSessionFactoryBuilder {
public DefaultSqlSessionFactory build(Reader reader) {
SAXReader saxReader = new SAXReader();
Document document = null;
try {
document = saxReader.read(new InputSource(reader));
// 拿到根標(biāo)簽元素
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文件對(duì)應(yīng)地址
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;
}
}測(cè)試驗(yàn)證
建表
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)建時(shí)間', `updateTime` datetime NOT NULL COMMENT '更新時(shí)間', 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)建時(shí)間
private Date updateTime; // 更新時(shí)間
}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>測(cè)試類
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();
}
}
}
總結(jié)
比mybatis小很多,取其(mybaits)精華來(lái)達(dá)到掌握ORM框架的目的
到此這篇關(guān)于SpringBoot中間件ORM框架實(shí)現(xiàn)案例詳解(Mybatis)的文章就介紹到這了,更多相關(guān)SpringBoot中間件ORM內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot整合RocketMQ批量發(fā)送消息的實(shí)現(xiàn)代碼
這篇文章主要介紹了SpringBoot整合RocketMQ批量發(fā)送消息的實(shí)現(xiàn),文中通過(guò)代碼示例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-04-04
Java遞歸算法詳解(動(dòng)力節(jié)點(diǎn)整理)
Java遞歸算法是基于Java語(yǔ)言實(shí)現(xiàn)的遞歸算法。遞歸算法對(duì)解決一大類問(wèn)題很有效,它可以使算法簡(jiǎn)潔和易于理解。接下來(lái)通過(guò)本文給大家介紹Java遞歸算法相關(guān)知識(shí),感興趣的朋友一起學(xué)習(xí)吧2017-03-03
java連接SQL?Server數(shù)據(jù)庫(kù)的超詳細(xì)教程
最近在java連接SQL數(shù)據(jù)庫(kù)時(shí)會(huì)出現(xiàn)一些問(wèn)題,所以這篇文章主要給大家介紹了關(guān)于java連接SQL?Server數(shù)據(jù)庫(kù)的超詳細(xì)教程,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
初識(shí)sa-token及登錄授權(quán)簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要為大家介紹了sa-token及登錄授權(quán)簡(jiǎn)單實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
SpringBoot中Jackson ObjectMapper應(yīng)用及說(shuō)明
SpringBoot支持支持Jackson作為默認(rèn)的JSON映射庫(kù),提供便捷的對(duì)象與JSON轉(zhuǎn)換功能,文章詳細(xì)介紹了ObjectMapper的配置與使用,以及SpringBoot中Jackson的多種自定義配置方法2026-05-05
idea下如何設(shè)置項(xiàng)目啟動(dòng)的JVM運(yùn)行內(nèi)存大小
這篇文章主要介紹了idea下如何設(shè)置項(xiàng)目啟動(dòng)的JVM運(yùn)行內(nèi)存大小問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
Spring?Boot虛擬線程Webflux在JWT驗(yàn)證和MySQL查詢性能比較
這篇文章主要為大家介紹了Spring Boot虛擬線程與Webflux在JWT驗(yàn)證和MySQL查詢上的性能比較,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
詳談@Cacheable不起作用的原因:bean未序列化問(wèn)題
這篇文章主要介紹了@Cacheable不起作用的原因:bean未序列化問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01

