mybatis插入與批量插入返回ID的原理詳解
背景
最近正在整理之前基于mybatis的半ORM框架。原本的框架底層類(lèi)ORM操作是通過(guò)StringBuilder的append拼接的,這次打算用JsqlParser重寫(xiě)一遍,一來(lái)底層不會(huì)存在太多的文本拼接,二來(lái)基于其他開(kāi)源包維護(hù)難度會(huì)小一些,最后還可以整理一下原有的冗余方法。
這兩天整理insert相關(guān)的方法,在將對(duì)象插入數(shù)據(jù)庫(kù)后,期望是要返回完整對(duì)象,并且包含實(shí)際的數(shù)據(jù)庫(kù)id。
基礎(chǔ)相關(guān)框架為:spring、mybatis、hikari。
底層調(diào)用方法
最底層的做法實(shí)際上很直白,就是利用mybatis執(zhí)行最簡(jiǎn)單的sql語(yǔ)句,給上代碼。
@Repository("baseDao") public class BaseDao extends SqlSessionDaoSupport { private Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 最大的單次批量插入的數(shù)量 */ private static final int MAX_BATCH_SIZE = 10000; @Override @Autowired public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { super.setSqlSessionFactory(sqlSessionFactory); } /** * 根據(jù)sql方法名稱(chēng)和對(duì)象插入數(shù)據(jù)庫(kù) */ public Object insert(String sqlName, Object obj) throws SQLException { return getSqlSession().insert(sqlName, obj); // 此處直接執(zhí)行傳入的xml中對(duì)應(yīng)的sql id,以及參數(shù) } }
單個(gè)對(duì)象插入
java代碼
/** * 簡(jiǎn)單插入實(shí)體對(duì)象 * * @param entity 實(shí)體對(duì)象 * @throws SQLException */ public <T extends BaseEntity> T insertEntity(T entity) throws SQLException { Insert insert = new Insert(); insert.setTable(new Table(entity.getClass().getSimpleName())); insert.setColumns(JsqlUtils.getColumnNameFromEntity(entity.getClass())); insert.setItemsList(JsqlUtils.getAllColumnValueFromEntity(entity,insert.getColumns())); Map<String, Object> param = new HashMap<>(); param.put("baseSql", insert.toString()); param.put("entity", entity); this.insert("BaseDao.insertEntity", param); return entity; }
xml代碼
<insert id="insertEntity" parameterType="java.util.Map" useGeneratedKeys="true" keyProperty="entity.id"> ${baseSql} </insert>
其他的就不多說(shuō)了,這里針對(duì)如何返回已經(jīng)入庫(kù)的id給個(gè)說(shuō)明。
在xml的 insert 標(biāo)簽中,設(shè)置 keyProperty 為 對(duì)應(yīng)對(duì)象的id字段,和 insert(sqlName, obj) 這個(gè)方法中的 obj 是對(duì)應(yīng)的。
這里一般有兩種情況:
直接保存實(shí)體的對(duì)象作為參數(shù)傳入(給偽代碼示例)
SaveObject saveObject = new SaveObject(); // SaveObject中包含字段soid,作為自增id saveObject.setName("my name"); saveObject.setNums(2); getSqlSession().insert("saveObject.insert",saveObject);
這種情況實(shí)際就是傳入了待保存的對(duì)象。這時(shí)候我們的xml應(yīng)該這樣
<insert id="insert" parameterType="SaveObject " useGeneratedKeys="true" keyProperty="soid"> insert into save_object (`name`,nums) values (#{names},#{nums}) </insert>
這里我們傳入了SaveObject實(shí)體對(duì)象作為參數(shù),所以我們的 keyProperty 就是parameter的id對(duì)應(yīng)的字段,在這里就是 soid 。
多個(gè)對(duì)象,實(shí)體對(duì)象作為其中一個(gè)對(duì)象傳入
Map<String, Object> param = new HashMap<>(); param.put("baseSql", insert.toString()); param.put("entity", entity); // 此處對(duì)應(yīng)實(shí)體作為map的第二個(gè)參數(shù)傳入 this.insert("BaseDao.insertEntity", param);
<insert id="insertEntity" parameterType="java.util.Map" useGeneratedKeys="true" keyProperty="entity.id"> ${baseSql} </insert>
這里也是比較容易理解,當(dāng)傳入?yún)?shù)是Map時(shí),我們的 keyProperty 對(duì)應(yīng)方式就是先從Map中讀出對(duì)應(yīng)value,再指向 value中的id字段。
列表批量插入
批量插入數(shù)據(jù)有兩種做法,一種是多次調(diào)用單個(gè)insert方法,這種效率較低就不說(shuō)了。另外一種是 insert into table (cols) values (val1),(val2),(val3)
這樣批量插入。
到mybatis中,也是分為兩種
直接保存實(shí)體的對(duì)象作為參數(shù)傳入(給偽代碼示例)
SaveObject saveObject1 = new SaveObject(); // SaveObject中包含字段soid,作為自增id saveObject1.setName("my name"); saveObject1.setNums(2); SaveObject saveObject2 = new SaveObject(); // SaveObject中包含字段soid,作為自增id saveObject2.setName("my name"); saveObject2.setNums(2); List<SaveObject> saveObjects = new ArrayList<SaveObject>(); saveObjects.add(saveObjects1); saveObjects.add(saveObjects2); getSqlSession().insert("saveObject.insertList",saveObjects);
這種情況實(shí)際就是傳入了待保存的對(duì)象。這時(shí)候我們的xml應(yīng)該這樣
<insert id="insertList" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="soid"> insert into save_object (`name`,nums) values <foreach collection="list" index="index" item="saveObject" separator=","> (#{saveObject.numsnames}, #{saveObject.nums}) </foreach> </insert>
多個(gè)對(duì)象,實(shí)體對(duì)象作為其中一個(gè)對(duì)象傳入
本文的重點(diǎn)來(lái)了,我自己卡在這里很久,反復(fù)調(diào)試才摸清邏輯。接下來(lái)就順著mybatis的思路來(lái)講,只會(huì)講id生成相關(guān)的,其他的流程就不多說(shuō)了。
先看這個(gè)類(lèi):org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
(很多代碼我用...代替了,不是特別重要,放在還占地方)
/** * 這個(gè)方法是在執(zhí)行完插入語(yǔ)句之后處理的,兩個(gè)關(guān)鍵參數(shù) * 1. MappedStatement ms 里面包含了我們的 keyProperty * 2. Object parameter 就是我們inser方法傳入的參數(shù) */ @Override public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { processBatch(ms, stmt, parameter); } public void processBatch(MappedStatement ms, Statement stmt, Object parameter) { final String[] keyProperties = ms.getKeyProperties(); if (keyProperties == null || keyProperties.length == 0) { return; } try (ResultSet rs = stmt.getGeneratedKeys()) { final Configuration configuration = ms.getConfiguration(); if (rs.getMetaData().getColumnCount() >= keyProperties.length) { Object soleParam = getSoleParameter(parameter); if (soleParam != null) { assignKeysToParam(configuration, rs, keyProperties, soleParam); } else { assignKeysToOneOfParams(configuration, rs, keyProperties, (Map<?, ?>) parameter); } } } catch (Exception e) { ... } } protected void assignKeysToOneOfParams(final Configuration configuration, ResultSet rs, final String[] keyProperties, Map<?, ?> paramMap) throws SQLException { // Assuming 'keyProperty' includes the parameter name. e.g. 'param.id'. int firstDot = keyProperties[0].indexOf('.'); if (firstDot == -1) { ... } String paramName = keyProperties[0].substring(0, firstDot); Object param; if (paramMap.containsKey(paramName)) { param = paramMap.get(paramName); } else { ... } ... assignKeysToParam(configuration, rs, modifiedKeyProperties, param); } private void assignKeysToParam(final Configuration configuration, ResultSet rs, final String[] keyProperties, Object param) throws SQLException { final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); final ResultSetMetaData rsmd = rs.getMetaData(); // Wrap the parameter in Collection to normalize the logic. Collection<?> paramAsCollection = null; if (param instanceof Object[]) { paramAsCollection = Arrays.asList((Object[]) param); } else if (!(param instanceof Collection)) { paramAsCollection = Arrays.asList(param); } else { paramAsCollection = (Collection<?>) param; } TypeHandler<?>[] typeHandlers = null; for (Object obj : paramAsCollection) { if (!rs.next()) { break; } MetaObject metaParam = configuration.newMetaObject(obj); if (typeHandlers == null) { typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd); } populateKeys(rs, metaParam, keyProperties, typeHandlers); } }
利用這個(gè)代碼先解釋一下上一節(jié) 直接保存實(shí)體的對(duì)象作為參數(shù)傳入 為什么id會(huì)被更新至實(shí)體內(nèi)的soid字段。
上一節(jié)的是 keyProperty="soid"
我們來(lái)看19行的代碼Object soleParam = getSoleParameter(parameter);
,當(dāng)我們傳入的對(duì)象是List的時(shí)候 soleParam != null,所以 直接執(zhí)行 assignKeysToParam 方法。
注意64和65行
for (Object obj : paramAsCollection) { if (!rs.next()) {
paramAsCollection 是將我們傳入的轉(zhuǎn)換為 Collection 類(lèi)型,所以這里是循環(huán)我們的給定實(shí)體列表參數(shù)。
rs就是ResultSet,就是插入之后的結(jié)果集。 rs.next()
就是指針指向下一條記錄,所以實(shí)際上這里是同步循環(huán),將結(jié)果集中的id直接設(shè)置到我們給的實(shí)體列表中
我們現(xiàn)在來(lái)看看多參數(shù)插入是會(huì)有什么問(wèn)題。
Java方法:
/** * 簡(jiǎn)單批量插入實(shí)體對(duì)象 * * @param entitys * @throws SQLException */ public List insertEntityList(List<? extends BaseEntity> entitys) throws SQLException { if (entitys == null || entitys.size() == 0) { return null; } Insert insert = new Insert(); insert.setTable(new Table(entitys.get(0).getClass().getSimpleName())); insert.setColumns(JsqlUtils.getColumnNameFromEntity(entitys.get(0).getClass())); MultiExpressionList multiExpressionList = new MultiExpressionList(); entitys.stream().map(e -> JsqlUtils.getAllColumnValueFromEntity(e,insert.getColumns())).forEach(e -> multiExpressionList.addExpressionList(e)); insert.setItemsList(multiExpressionList); Map<String, Object> param = new HashMap<>(); param.put("baseSql", insert.toString()); param.put("list", entitys); this.insert("BaseDao.insertEntityList", param); return entitys; }
Xml:
<insert id="insertEntityList" parameterType="java.util.Map" useGeneratedKeys="true" keyProperty="id"> ${baseSql} </insert>
會(huì)有什么問(wèn)題??根據(jù)這樣的xml,最后的結(jié)果是我們傳入的map中會(huì)多一個(gè)key 叫 “id”,里面存的是一個(gè)插入的實(shí)體的id。
因?yàn)楦鶕?jù)源碼 Map并非 Collection 類(lèi)型,所以會(huì)做為只有一個(gè)元素的數(shù)組傳入,在剛才同步循環(huán)的地方就只會(huì)循環(huán)一次,把結(jié)果集中第一條數(shù)據(jù)的id放進(jìn)map中,循環(huán)就結(jié)束了。
怎么解決呢??
解決的方法就在 assignKeysToOneOfParams 這個(gè)方法,方法名其實(shí)已經(jīng)說(shuō)了,將主鍵賦給其中一個(gè)參數(shù),這里確實(shí)也是取了其中的一個(gè)參數(shù)進(jìn)行賦值主鍵。所以我們只要能夠跳轉(zhuǎn)到這個(gè)方法就好。所以需要滿(mǎn)足 getSoleParameter(parameter) == null
,點(diǎn)進(jìn)代碼看
private Object getSoleParameter(Object parameter) { if (!(parameter instanceof ParamMap || parameter instanceof StrictMap)) { return parameter; } Object soleParam = null; for (Object paramValue : ((Map<?, ?>) parameter).values()) { if (soleParam == null) { soleParam = paramValue; } else if (soleParam != paramValue) { soleParam = null; break; } } return soleParam; }
要返回null,條件是這樣:
- 參數(shù)是ParamMap或者 StrictMap
- 參數(shù)大于兩個(gè),且第一個(gè)和后面任意一個(gè)不相等
所以解決方案出爐,很簡(jiǎn)單,只需要改動(dòng)代碼兩個(gè)地方即可。
/** * 簡(jiǎn)單批量插入實(shí)體對(duì)象 * * @param entitys * @throws SQLException */ public List insertEntityList(List<? extends BaseEntity> entitys) throws SQLException { if (entitys == null || entitys.size() == 0) { return null; } Insert insert = new Insert(); insert.setTable(new Table(entitys.get(0).getClass().getSimpleName())); insert.setColumns(JsqlUtils.getColumnNameFromEntity(entitys.get(0).getClass())); MultiExpressionList multiExpressionList = new MultiExpressionList(); entitys.stream().map(e -> JsqlUtils.getAllColumnValueFromEntity(e,insert.getColumns())).forEach(e -> multiExpressionList.addExpressionList(e)); insert.setItemsList(multiExpressionList); Map<String, Object> param = new MapperMethod.ParamMap<>(); // 這里替換為 MapperMethod.ParamMap 類(lèi)型 param.put("baseSql", insert.toString()); param.put("list", entitys); this.insert("BaseDao.insertEntityList", param); return entitys; }
Xml:
<insert id="insertEntityList" parameterType="java.util.Map" useGeneratedKeys="true" keyProperty="list.id"> <!-- 這里是map中的key.實(shí)體id --> ${baseSql} </insert>
完成
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
- mybatis中批量插入的兩種方式(高效插入)
- MyBatis批量插入(insert)數(shù)據(jù)操作
- Mybatis三種批量插入數(shù)據(jù)的方式
- MyBatis-Plus 批量插入數(shù)據(jù)的操作方法
- MyBatis批量插入大量數(shù)據(jù)(1w以上)
- MyBatis批量插入數(shù)據(jù)的三種方法實(shí)例
- 給你的MyBatis-Plus裝上批量插入的翅膀(推薦)
- MyBatis批量插入數(shù)據(jù)到Oracle數(shù)據(jù)庫(kù)中的兩種方式(實(shí)例代碼)
- Mybatis-plus 批量插入太慢的問(wèn)題解決(提升插入性能)
- Mybatis批量插入的三種實(shí)現(xiàn)方法
相關(guān)文章
java+selenium實(shí)現(xiàn)滑塊驗(yàn)證
現(xiàn)在越來(lái)越多的網(wǎng)站都使用采用滑塊驗(yàn)證來(lái)作為驗(yàn)證機(jī)制,用于判斷用戶(hù)是否為人類(lèi)而不是機(jī)器人,本文就將利用java和selenium實(shí)現(xiàn)滑塊驗(yàn)證,希望對(duì)大家有所幫助2023-12-12Java數(shù)據(jù)結(jié)構(gòu)最清晰圖解二叉樹(shù)前 中 后序遍歷
樹(shù)是一種重要的非線(xiàn)性數(shù)據(jù)結(jié)構(gòu),直觀地看,它是數(shù)據(jù)元素(在樹(shù)中稱(chēng)為結(jié)點(diǎn))按分支關(guān)系組織起來(lái)的結(jié)構(gòu),很象自然界中的樹(shù)那樣。樹(shù)結(jié)構(gòu)在客觀世界中廣泛存在,如人類(lèi)社會(huì)的族譜和各種社會(huì)組織機(jī)構(gòu)都可用樹(shù)形象表示2022-01-01MyBatis Log 插件無(wú)法顯示SQL語(yǔ)句的原因解析
MyBatis Log是IDEA一款下載量非常高的插件,該插件可以對(duì)控制臺(tái)打印的日志進(jìn)行解析,然后將對(duì)應(yīng)的SQL語(yǔ)句整理并拼接好對(duì)應(yīng)的參數(shù),非常方便。這篇文章給大家介紹MyBatis Log 插件無(wú)法顯示SQL語(yǔ)句的原因,感興趣的朋友跟隨小編一起看看吧2020-09-09淺談java中math類(lèi)中三種取整函數(shù)的區(qū)別
下面小編就為大家?guī)?lái)一篇淺談java中math類(lèi)中三種取整函數(shù)的區(qū)別。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11Java快速入門(mén)掌握類(lèi)與對(duì)象及變量的使用
類(lèi)和對(duì)象是兩種以計(jì)算機(jī)為載體的計(jì)算機(jī)語(yǔ)言的合稱(chēng)。對(duì)象是對(duì)客觀事物的抽象,類(lèi)是對(duì)對(duì)象的抽象。類(lèi)是一種抽象的數(shù)據(jù)類(lèi)型;變量就是可以變化的量,存儲(chǔ)在內(nèi)存中—個(gè)可以擁有在某個(gè)范圍內(nèi)的可變存儲(chǔ)區(qū)域2022-04-04Springboot中攔截GET請(qǐng)求獲取請(qǐng)求參數(shù)驗(yàn)證合法性核心方法
這篇文章主要介紹了Springboot中攔截GET請(qǐng)求獲取請(qǐng)求參數(shù)驗(yàn)證合法性,在Springboot中創(chuàng)建攔截器攔截所有GET類(lèi)型請(qǐng)求,獲取請(qǐng)求參數(shù)驗(yàn)證內(nèi)容合法性防止SQL注入,這種方法適用攔截get類(lèi)型請(qǐng)求,需要的朋友可以參考下2023-08-08Java中Integer的parseInt和valueOf的區(qū)別詳解
這篇文章主要介紹了Java中Integer的parseInt和valueOf的區(qū)別詳解,nteger.parseInt(s)是把字符串解析成int基本類(lèi)型,Integer.valueOf(s)是把字符串解析成Integer對(duì)象類(lèi)型,其實(shí)int就是Integer解包裝,Integer就是int的包裝,需要的朋友可以參考下2023-11-11java獲取版本號(hào)及字節(jié)碼編譯版本方法示例
這篇文章主要給大家介紹了關(guān)于java獲得版本號(hào)及字節(jié)碼編譯版本的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10Spring Boot REST國(guó)際化的實(shí)現(xiàn)代碼
本文我們將討論如何在現(xiàn)有的Spring Boot項(xiàng)目中添加國(guó)際化。只需幾個(gè)簡(jiǎn)單的步驟即可實(shí)現(xiàn)Spring Boot應(yīng)用的國(guó)際化,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10