tk.mybatis如何擴展自己的通用mapper
tk.mybatis擴展自己的通用mapper
目的:tk.mybatis 提供的通用mapper,雖然使用方便,不過在有些sql還是不能滿足我們的需要的,而且我們希望對於deleted語句進行管控(因為通用mapper提供的都是物理刪除,有時我們對一些敏感數據隻能進行邏輯刪除),因此我們將基於原有的通用mapper進行自己的擴展
1 jar引入
<!-- 非springboot --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> <version>3.4.0</version> </dependency>
非springboot項目引這個包
<!-- springboot --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.0.2</version> </dependency>
springboot項目直接引這個包,本次我們將基於springboot進行開發,這裡註意jar包版本,過低的版本將會出現問題,下面會介紹
2 編寫配置類
package com.example.configuration; import com.example.common.mapper.commonMapper.CommonMapper; import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import tk.mybatis.spring.mapper.MapperScannerConfigurer; import java.util.Properties; @Configuration @AutoConfigureAfter(MybatisAutoConfiguration.class) public class MyBatisConfiguration { @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory"); mapperScannerConfigurer.setBasePackage("com.example.dao.mapper"); Properties properties = new Properties(); properties.setProperty("mappers", CommonMapper.class.getName()); properties.setProperty("notEmpty", "false"); properties.setProperty("identity", "MYSQL"); properties.setProperty("order","BEFORE"); mapperScannerConfigurer.setProperties(properties); return mapperScannerConfigurer; } }
這裡要註意兩點:
1>CommonMapper這個是我們自己定義的通用mapper,它放置一個單獨的包下面。也就是說這個包下面隻有這個一個mapper,否則我們在實用泛型時會出現類型轉換的問題。(我在這裡糾結瞭好久)
2>這裡我們配置瞭自定義mapper的掃描路徑。註意springboot項目一定要註意jar包版本,版本過低的話,在配置類中的定義是沒有用的。還是掃描不到,我之前使用的版本1.2.3就會出現這個問題,更換更高的版本就可以解決瞭。
3 常規方法接口整合到自己的自定義Mapper接口上
因為我們使用的是mysql數據庫,所以在使用通用功能的時候就選擇性引入一些經常使用的方法,下面是自己定義的常用mapper類的整合(這裡會拋棄一些不常用的類或不是mysql的方法類)。這些基類不要放在 BasePackage的路徑下,這個路徑下的mapper都是要指定明確的泛型類型的。
1 > 提供查詢功能Mapper:
package com.example.common.mapper.basics; import tk.mybatis.mapper.common.Marker; import tk.mybatis.mapper.common.base.select.*; import tk.mybatis.mapper.common.condition.SelectByConditionMapper; import tk.mybatis.mapper.common.condition.SelectCountByConditionMapper; import tk.mybatis.mapper.common.example.SelectByExampleMapper; import tk.mybatis.mapper.common.ids.SelectByIdsMapper; /** * @desc 基礎查詢功能mapper * */ public interface SelectMapper<T> extends Marker, SelectOneMapper<T>, tk.mybatis.mapper.common.base.select.SelectMapper<T>, SelectAllMapper<T>, SelectCountMapper<T>, SelectByPrimaryKeyMapper<T>, ExistsWithPrimaryKeyMapper<T>, SelectByIdsMapper<T>, SelectByConditionMapper<T>, SelectCountByConditionMapper<T>, SelectByExampleMapper<T> { }
2>提供新增功能mapper
package com.example.common.mapper.basics; import tk.mybatis.mapper.common.Marker; import tk.mybatis.mapper.common.MySqlMapper; import tk.mybatis.mapper.common.base.insert.InsertSelectiveMapper; /** * @desc 基礎插入功能mapper */ public interface InsertMapper<T> extends Marker, tk.mybatis.mapper.common.base.insert.InsertMapper<T>, InsertSelectiveMapper<T>, MySqlMapper<T>{ }
3>提供更新功能mapper
package com.example.common.mapper.basics; import tk.mybatis.mapper.common.Marker; import tk.mybatis.mapper.common.base.update.UpdateByPrimaryKeyMapper; import tk.mybatis.mapper.common.base.update.UpdateByPrimaryKeySelectiveMapper; import tk.mybatis.mapper.common.condition.UpdateByConditionMapper; import tk.mybatis.mapper.common.condition.UpdateByConditionSelectiveMapper; import tk.mybatis.mapper.common.example.UpdateByExampleSelectiveMapper; /** * @desc 基礎更新功能mapper * */ public interface UpdateMapper<T> extends Marker, UpdateByPrimaryKeyMapper<T>, UpdateByPrimaryKeySelectiveMapper<T>, UpdateByConditionMapper<T>, UpdateByConditionSelectiveMapper<T>, UpdateByExampleSelectiveMapper<T> { }
4>提供刪除功能mapper
package com.example.common.mapper.basics; import com.example.common.mapper.defined.DeleteShamByIdsMapper; import tk.mybatis.mapper.common.Marker; import tk.mybatis.mapper.common.base.delete.DeleteByPrimaryKeyMapper; import tk.mybatis.mapper.common.condition.DeleteByConditionMapper; import tk.mybatis.mapper.common.ids.DeleteByIdsMapper; /** * @desc 基礎刪除功能mapper * */ public interface DeleteMapper<T> extends Marker, tk.mybatis.mapper.common.base.delete.DeleteMapper<T>, DeleteByPrimaryKeyMapper<T>, DeleteByConditionMapper<T>, DeleteByIdsMapper<T>{ }
5>提供增刪改查的基類 CommonMapper
package com.example.common.mapper.commonMapper; import com.example.common.mapper.basics.DeleteMapper; import com.example.common.mapper.basics.InsertMapper; import com.example.common.mapper.basics.SelectMapper; import com.example.common.mapper.basics.UpdateMapper; /** * @param <T> * @desc 基礎增刪改查功能mapper */ public interface CommonMapper<T> extends DeleteMapper<T>, InsertMapper<T>, SelectMapper<T>, UpdateMapper<T> { }
6>實體對象
package com.example.dao.entity; import lombok.Data; import javax.persistence.Id; import javax.persistence.Table; import java.io.Serializable; @Table(name="user") @Data public class User implements Serializable { @Id private Integer id; private String trueName; private String userName; private Integer isDeleted; }
7>讓自定義mapper繼承CommenMapper,就可以使用這些語句瞭
package com.example.dao.mapper; import com.example.common.mapper.commonMapper.CommonMapper; import com.example.dao.entity.User; public interface UserMapper extends CommonMapper<User> { }
接下來我們會對 CommonMapper中提供刪除方法的DeleteMapper進行增強,來給他添加邏輯刪除的語句
1>我們自定義的Provider需要去繼承 MapperTemplate並覆寫構造方法
這個是源碼中的批量刪除
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package tk.mybatis.mapper.provider; import java.util.Set; import org.apache.ibatis.mapping.MappedStatement; import tk.mybatis.mapper.MapperException; import tk.mybatis.mapper.entity.EntityColumn; import tk.mybatis.mapper.mapperhelper.EntityHelper; import tk.mybatis.mapper.mapperhelper.MapperHelper; import tk.mybatis.mapper.mapperhelper.MapperTemplate; import tk.mybatis.mapper.mapperhelper.SqlHelper; public class IdsProvider extends MapperTemplate { public IdsProvider(Class<?> mapperClass, MapperHelper mapperHelper) { super(mapperClass, mapperHelper); } public String deleteByIds(MappedStatement ms) { Class<?> entityClass = this.getEntityClass(ms); StringBuilder sql = new StringBuilder(); sql.append(SqlHelper.deleteFromTable(entityClass, this.tableName(entityClass))); Set<EntityColumn> columnList = EntityHelper.getPKColumns(entityClass); if (columnList.size() == 1) { EntityColumn column = (EntityColumn)columnList.iterator().next(); sql.append(" where "); sql.append(column.getColumn()); sql.append(" in (${_parameter})"); return sql.toString(); } else { throw new MapperException("繼承 deleteByIds 方法的實體類[" + entityClass.getCanonicalName() + "]中必須隻有一個帶有 @Id 註解的字段"); } } public String selectByIds(MappedStatement ms) { Class<?> entityClass = this.getEntityClass(ms); this.setResultType(ms, entityClass); StringBuilder sql = new StringBuilder(); sql.append(SqlHelper.selectAllColumns(entityClass)); sql.append(SqlHelper.fromTable(entityClass, this.tableName(entityClass))); Set<EntityColumn> columnList = EntityHelper.getPKColumns(entityClass); if (columnList.size() == 1) { EntityColumn column = (EntityColumn)columnList.iterator().next(); sql.append(" where "); sql.append(column.getColumn()); sql.append(" in (${_parameter})"); return sql.toString(); } else { throw new MapperException("繼承 selectByIds 方法的實體類[" + entityClass.getCanonicalName() + "]中必須隻有一個帶有 @Id 註解的字段"); } } }
接下來我們對IdsProvider 進行擴展使他支持批量邏輯刪除
1>創建IdsProvider的子類
package com.example.common.mapper.defined; import org.apache.ibatis.mapping.MappedStatement; import tk.mybatis.mapper.MapperException; import tk.mybatis.mapper.entity.EntityColumn; import tk.mybatis.mapper.mapperhelper.EntityHelper; import tk.mybatis.mapper.mapperhelper.MapperHelper; import tk.mybatis.mapper.mapperhelper.SqlHelper; import tk.mybatis.mapper.provider.IdsProvider; import java.util.Set; public class IdsProviderDefined extends IdsProvider { public IdsProviderDefined(Class<?> mapperClass, MapperHelper mapperHelper) { super(mapperClass, mapperHelper); } public String deleteShamByIds(MappedStatement ms) { Class<?> entityClass = this.getEntityClass(ms); StringBuilder sql = new StringBuilder(); sql.append(SqlHelper.updateTable(entityClass, this.tableName(entityClass))); sql.append("<set> is_deleted='1' </set>"); Set<EntityColumn> columnList = EntityHelper.getPKColumns(entityClass); if (columnList.size() == 1) { EntityColumn column = (EntityColumn)columnList.iterator().next(); sql.append(" where "); sql.append(column.getColumn()); sql.append(" in (${_parameter})"); return sql.toString(); } else { throw new MapperException("繼承 deleteByIds 方法的實體類[" + entityClass.getCanonicalName() + "]中必須隻有一個帶有 @Id 註解的字段"); } } }
deleteShamByIds就是我們門批量邏輯刪除的方法,這個方法其實就是再拼裝sql,tk中提供瞭很多幫助我們拼裝sql,和參數的類(SqlHelper,EntityHelper),我們可以使用
2>創建DeleteShamByIdsMapper接口
package com.example.common.mapper.defined; import org.apache.ibatis.annotations.DeleteProvider; public interface DeleteShamByIdsMapper<T> { @DeleteProvider( type = IdsProviderDefined.class, method = "dynamicSQL" ) int deleteShamByIds(String var1); //這裡的抽象方法的名稱必須和IdsProviderDefined 中的方法一致 }
3>在DeleteMapper中繼承我們自定義的邏輯刪除接口DeleteShamByIdsMapper
package com.example.common.mapper.basics; import com.example.common.mapper.defined.DeleteShamByIdsMapper; import tk.mybatis.mapper.common.Marker; import tk.mybatis.mapper.common.base.delete.DeleteByPrimaryKeyMapper; import tk.mybatis.mapper.common.condition.DeleteByConditionMapper; import tk.mybatis.mapper.common.ids.DeleteByIdsMapper; /** * @desc 基礎刪除功能mapper * */ public interface DeleteMapper<T> extends Marker, tk.mybatis.mapper.common.base.delete.DeleteMapper<T>, DeleteByPrimaryKeyMapper<T>, DeleteByConditionMapper<T>, DeleteByIdsMapper<T>, DeleteShamByIdsMapper<T>{ }
到這裡我們自定義的批量邏輯刪除就定義好瞭,我們可以通過這種方式擴展通用mapper。
TK mybatis插件通用mapper與oracle的幾個坑
最近公司有幾個項目的數據庫用的oracle,有段時間沒用,然後果斷就掉坑裡面瞭,記錄幾個比較有代表性的。
1:關於oracle的批量數據插入,我用TK通用mapper的insertList方法自動拼接出來的sql是這樣的
insert into table(column1,column2)values (value1,value2), (value3,value4)。。。
咋一看沒啥問題啊,然後就一直報 sql未正確結束,出於對自己sql的過於自信,導致一直都在思考是否是參數問題,浪費瞭好幾個小時,後來把sql拼好直接丟到數據庫跑瞭跑才發現,這種sql確實是不能在oracle跑的,oracle批量插入的語法應該是:
INSERT ALL INTO A(field_1,field_2) VALUES (value_1,value_2) INTO A(field_1,field_2) VALUES (value_3,value_4) INTO A(field_1,field_2) VALUES (value_5,value_6)
粗略的瞅瞭下TK的代碼,貌似確實沒有校驗數據庫驅動就直接開始拼sql瞭
public String insertList(MappedStatement ms) { Class<?> entityClass = this.getEntityClass(ms); StringBuilder sql = new StringBuilder(); sql.append(SqlHelper.insertIntoTable(entityClass, this.tableName(entityClass))); sql.append(SqlHelper.insertColumns(entityClass, true, false, false)); sql.append(" VALUES "); sql.append("<foreach collection=\"list\" item=\"record\" separator=\",\" >"); sql.append("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">"); Set<EntityColumn> columnList = EntityHelper.getColumns(entityClass); Iterator var5 = columnList.iterator(); while(var5.hasNext()) { EntityColumn column = (EntityColumn)var5.next(); if (!column.isId() && column.isInsertable()) { sql.append(column.getColumnHolder("record") + ","); } } sql.append("</trim>"); sql.append("</foreach>"); return sql.toString(); }
不過我覺得這種插件肯定是有兼容方案的,並且使用瞭這個插件跟使用源生mybatis並不沖突,所以實際對開發並沒有影響,算是復習一下oracle的基礎並提醒下自己,程序員還是老老實實先run起來,自以為是的經驗有時候也很坑的。
2:關於oracle的比較符號(<> =)和null
先復習一下
一般數據庫查詢某某字段為空或者不為空的時候都是用is null或者is not null
如果查詢條件使用=null或者<>null是查不出來數據的
但是賦值卻可以用=null如
update BUS_PLATFORM.bus_trans set return_code = null where return_msg= 'tac驗證通過';
這個是可以執行成功的,不過這個都是表外的值操作,那麼當表內的數據有null時,又有啥坑呢,比如
select * from table1 where name <>'zhansan'
查詢表中name字段不為zhangsan的數據,當表中有name為null的數據時,這些數據也是查不出來的.比如表中有100條數據,一條數據的name值為zhangsan ,九條數據的name值為null,那麼這條sql隻能查出90條數據,所以如果確切的需求要查詢name值不為zhangsan的數據且包括name值為null的數據,sql應該為
select * from table1 where name <>'zhansan' or name is null
3:這個是mybatis的一個規范,基本是湊數的,不過也坑過我
先上內容:mybatis插入null值時需要指定該值的類型(jdbctype),不然會報錯。
因為不指定類型的話mybatis會自己去適配匹配的數據庫字段類型,null適配不瞭。搞不定就報錯,很穩。不過一般使用xml配置sql的話一般都不會有這個問題,寫的時候順手都會寫上。這個也是我使用TK的時候暴露出來的問題,在使用insert方法的時候有null就掛瞭,不過TK有個insertSelective方法做插入的時候會自動過濾掉空值。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Mybatis如何使用@Mapper和@MapperScan註解實現映射關系
- SpringBoot集成Mybatis+xml格式的sql配置文件操作
- ssm mybatis如何配置多個mapper目錄
- 解決mybatis中的mapper命名問題
- 解決tk mapper 通用mapper的bug問題