SpringBoot整合mybatis通用Mapper+自定義通用Mapper方法解析
最近公司在用的通用mapper,自己感興趣,然后就來搭建了一個springboot項目試驗通用mapper
這個項目是國內(nèi)的大神寫的一個mybatis插件,里面有很多的增刪改查方法
官方解釋的是通用mapper支持3.2.4以及以上的版本
首先引入pom
<!--Mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator</artifactId> <version>1.3.5</version> <type>pom</type> </dependency> <!--分頁插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.2.1</version> </dependency> <!--tkmybatis --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>1.1.4</version> </dependency>
通用Mapper是tk.mybais中的
配置文件application.yml:
server: port: 8081 # 下面是配置undertow作為服務(wù)器的參數(shù) undertow: # 設(shè)置IO線程數(shù), 它主要執(zhí)行非阻塞的任務(wù),它們會負責多個連接, 默認設(shè)置每個CPU核心一個線程 io-threads: 4 # 阻塞任務(wù)線程池, 當執(zhí)行類似servlet請求阻塞操作, undertow會從這個線程池中取得線程,它的值設(shè)置取決于系統(tǒng)的負載 worker-threads: 20 # 以下的配置會影響buffer,這些buffer會用于服務(wù)器連接的IO操作,有點類似netty的池化內(nèi)存管理 # 每塊buffer的空間大小,越小的空間被利用越充分 buffer-size: 1024 # 是否分配的直接內(nèi)存 direct-buffers: true spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.jdbc.Driver driver-class-name: com.mysql.jdbc.Driver platform: mysql url: jdbc:mysql://xxx.xxx.xxx.xxx:5306/miniprogram?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false username: xxxxx password: xxxxx initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT1FROMDUAL testWhileIdle: true testOnBorrow: false testOnReturn: false filters: stat,wall logSlowSql: true redis: database: 1 host: xxxxx port: xxxx password: xxxx timeout: 10000 activemq: queueName: mvp.queue topicName: mvp.topic #賬號密碼 user: user password: user #URL of the ActiveMQ broker. broker-url: tcp://localhost:61616 in-memory: false #必須使用連接池 pool: #啟用連接池 enabled: true #連接池最大連接數(shù) max-connections: 5 #空閑的連接過期時間,默認為30秒 idle-timeout: 30s # jedis: 有默認值,源碼:RedisProperties # pool: # max-active: # max-idle: # max-wait: # min-idle: mybatis: typeAliasesPackage: com.pinyu.miniprogram.mysql.entity mapper-locations: classpath:mapper/**/*Mapper.xml mapper: mappers: com.pinyu.miniprogram.mysql.mappers.BaseMapper identity: mysql #logging.config: # classpath: test/log4j2_test.xml
xxxx請配置自己的數(shù)據(jù)庫相關(guān)信息
mapper: ? mappers: com.pinyu.miniprogram.mysql.mappers.BaseMapper ? identity: mysql
ctrl+鼠標點擊com.pinyu.miniprogram.mysql.mappers.BaseMapper 進入源碼可以看到 MapperProperties類
是一個集合,意思這里可以mappers配置多個通用Mapper,可以是直接繼承它已有的通用Mapper,也可以是定義自己需要的通用Mapper,自定義通用Mapper(代替它的Mapper)繼承實現(xiàn)的方式不一樣,下面會講到
也可以用代碼進行配置
/** * 通用mapper與分頁插件的一些配置 */ @Configuration public class MyBatisMapperScannerConfig { /** * 使用通用Mapper之前需要初始化的一些信息 * 使用通用Mapper插件時請勿使用熱加載,否則報錯,插件作者后續(xù)應(yīng)該會修復 */ @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory"); mapperScannerConfigurer.setBasePackage("com.xx.xx.xx.mapper");//普通mapper的位置 Properties properties = new Properties(); properties.setProperty("mappers", BaseMapper.class.getName());//通用mapper的全名 properties.setProperty("notEmpty", "false"); properties.setProperty("IDENTITY", "MYSQL");//配置數(shù)據(jù)庫方言 mapperScannerConfigurer.setProperties(properties); return mapperScannerConfigurer; } /** * 配置mybatis的分頁插件pageHelper */ @Bean public PageHelper pageHelper(){ PageHelper pageHelper = new PageHelper(); Properties properties = new Properties(); //設(shè)置為true時,會將RowBounds第一個參數(shù)offset當成pageNum頁碼使用 properties.setProperty("offsetAsPageNum","true"); //置為true時,使用RowBounds分頁會進行count查詢 properties.setProperty("rowBoundsWithCount","true"); //合理化查詢,啟用合理化時, //如果pageNum<1會查詢第一頁,如果pageNum>pages會查詢最后一頁 //未開啟時如果pageNum<1或pageNum>pages會返回空數(shù)據(jù) properties.setProperty("reasonable","true"); //配置mysql數(shù)據(jù)庫的方言 properties.setProperty("dialect","mysql"); pageHelper.setProperties(properties); return pageHelper; } }
mappers對應(yīng)的通用mapper類,不要和自己其他業(yè)務(wù)的mapper放在一起,不要被@MapperScan掃描到,不然會報錯,因為繼承了通用mapper,會有很多相應(yīng)的方法,被掃描到以后,mybatis發(fā)現(xiàn)沒有一個xml配置文件或者相應(yīng)方法沒有進行實現(xiàn),這時候就會報錯。但是繼承自己的BaseMapper相關(guān)mapper肯定是要被掃描到的
數(shù)據(jù)庫創(chuàng)建一張表member以及相關(guān)字段
/* Navicat MySQL Data Transfer Source Server : 120.79.81.103-5306-master Source Server Version : 50719 Source Host : 120.79.81.103:5306 Source Database : miniprogram Target Server Type : MYSQL Target Server Version : 50719 File Encoding : 65001 Date: 2019-04-03 23:09:51 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for member -- ---------------------------- DROP TABLE IF EXISTS `member`; CREATE TABLE `member` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `member_name` varchar(255) NOT NULL COMMENT '會員用戶名', `tel` varchar(255) DEFAULT NULL COMMENT '電話', `nick_name` varchar(255) NOT NULL COMMENT '昵稱', `head_img` varchar(255) DEFAULT NULL COMMENT '頭像地址', `status` int(1) NOT NULL COMMENT '狀態(tài) 1啟用 2禁用', `create_date` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間', `update_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間', `pwd` varchar(255) NOT NULL COMMENT '密碼', `signature` varchar(255) DEFAULT NULL COMMENT '個性簽名', `creat_id` bigint(20) DEFAULT NULL, `delete_state` int(1) NOT NULL DEFAULT '1', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of member -- ---------------------------- INSERT INTO `member` VALUES ('1', 'dsada', '15928878433', 'dasdas', null, '1', '2019-03-11 18:47:53', '2019-03-11 18:47:53', '123456', null, null, '1'); INSERT INTO `member` VALUES ('2', 'ypp', '15928878888', '6666', null, '1', '2019-03-11 19:35:39', null, 'EDGM@MAMABDACFDLLG', null, null, '1');
創(chuàng)建實體MemberEntity,一般創(chuàng)建實體我都會抽一些相同的字段出來
MemberEntity:
package com.pinyu.miniprogram.mysql.entity.member; import javax.persistence.Table; import com.pinyu.miniprogram.mysql.entity.BaseEntity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; @Data // getter、setter @AllArgsConstructor // 全參構(gòu)造方法 @NoArgsConstructor // 無參構(gòu)造方法 @Accessors(chain = true) // 鏈式編程寫法 @Table(name="member") public class MemberEntity extends BaseEntity { /** * */ private static final long serialVersionUID = -2601234073734313278L; private String memberName;// 會員登錄用戶名 private String nickName;// 昵稱 private String tel;// 電話 private String pwd;// 密碼 private String headImg;// 頭像圖片 private String signature;// 個性簽名 private Integer status;//狀態(tài) 1禁用 2啟用 }
BaseEntity:
package com.pinyu.miniprogram.mysql.entity; import java.util.Date; import javax.persistence.Column; import com.pinyu.miniprogram.mysql.entity.member.MemberEntity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** * @author ypp * @Description: TODO(用一句話描述該文件做什么) */ @Data // getter、setter @AllArgsConstructor // 全參構(gòu)造方法 @NoArgsConstructor // 無參構(gòu)造方法 @Accessors(chain = true) // 鏈式編程寫法 public class BaseEntity extends IdEntity { /** * */ private static final long serialVersionUID = 8575696766261326260L; @Column(name="creat_id") private Integer creatId; @Column(name="create_date") private Date createDate; @Column(name="delete_state") private Integer deleteState;// 刪除狀態(tài) 1正常 2已刪除 }
IdEntity:
package com.pinyu.miniprogram.mysql.entity; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import com.pinyu.miniprogram.mysql.entity.member.MemberEntity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** * @author ypp * @Description: TODO(用一句話描述該文件做什么) */ @Data // getter、setter @AllArgsConstructor // 全參構(gòu)造方法 @NoArgsConstructor // 無參構(gòu)造方法 @Accessors(chain = true) // 鏈式編程寫法 public class IdEntity implements Serializable { /** * */ private static final long serialVersionUID = -9089706482760436909L; @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; }
@Table
:對應(yīng)數(shù)據(jù)庫的表,如果不寫默認是類名首字母小寫作為表名,比Member,不寫數(shù)據(jù)庫表默認指向member@Column
:的屬性name對應(yīng)數(shù)據(jù)庫表的字段,如果不寫默認是駝峰下劃線匹配,比如private Long myId,如果不寫得話,就是對應(yīng)數(shù)據(jù)庫表字段my_id@Id
:把當前字段作為數(shù)據(jù)庫主鍵使用,匹配數(shù)據(jù)庫主鍵。如果不貼此注解,在某些查詢語句的時候會把表字段一起作為聯(lián)合主鍵查詢@GeneratedValue
:讓通用mapper在執(zhí)行insert操作之后將自動生成的主鍵值回寫到當前實體對象對應(yīng)的屬性當中
新建一個通用Mapper繼承Mapper、MySqlMapper
點擊進去看
package com.pinyu.miniprogram.mysql.mappers; import com.pinyu.miniprogram.mysql.entity.BaseEntity; import tk.mybatis.mapper.common.Mapper; import tk.mybatis.mapper.common.MySqlMapper; /** * @author ypp * 創(chuàng)建時間:2018年12月27日 下午1:29:03 * @Description: TODO(用一句話描述該文件做什么) */ public interface BaseMapper<T extends BaseEntity> extends Mapper<T>,MySqlMapper<T>{ }
BaseMapper是我自己定義的通用Mapper,注意在被繼承Mapper上面也有BaseMapper,注意區(qū)分,我這里名字取一樣了而已。
被Mapper繼承的BaseMapper里面有還有很多增刪改查的方法
這是源碼
看了下,里面有大概20個左右方法,都是比較基礎(chǔ)的增刪改查
測試:
MemberMapper并沒有selectAll方法,沿用的繼承的selectAll方法
比較詳細的一個入門示例
希望能幫助到用到的小伙伴
自定義通用Mapper,也有可能在實際工作中通用Mapper并不能滿足工作,需要額外的一些通用方法,但是這種的情況很少,通用Mapper提供的方法基本都能滿足單表操作需求了
舉例我要寫一個通用的單表分頁
1、自己定義的通用Mapper必須包含泛型,例如MysqlMapper<T>。這一點在這里可以忽略,這里并沒有自定義自己的通用Mapper,而是使用了它自帶的通用Mapper,我們繼承的它
2、自定義的通用Mapper接口中的方法需要有合適的注解。具體可以參考Mapper
3、需要繼承MapperTemplate來實現(xiàn)具體的操作方法。必須要新建一個類繼承MapperTemplate,必須繼承MapperTemplate,必須繼承MapperTemplate
4、通用Mapper中的Provider一類的注解只能使用相同的type類型(這個類型就是第三個要實現(xiàn)的類。)。實際上method也都寫的一樣。
在自己的BaseMapper寫一個方法
改造后的BaseMapper
package com.pinyu.miniprogram.mysql.mappers; import java.util.List; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.SelectProvider; import com.pinyu.miniprogram.mysql.entity.BaseEntity; import tk.mybatis.mapper.common.Mapper; import tk.mybatis.mapper.common.MySqlMapper; /** * @author ypp 創(chuàng)建時間:2018年12月27日 下午1:29:03 * @Description: TODO(用一句話描述該文件做什么) */ public interface BaseMapper<T extends BaseEntity> extends Mapper<T>, MySqlMapper<T> { /** * * 單表分頁查詢 * * @param object * @param offset * @param limit * @return */ @SelectProvider(type = BaseMapperProvider.class, method = "dynamicSQL") List selectPage(@Param("entity") T object, @Param("offset") int offset, @Param("limit") int limit); }
返回結(jié)果為List,入?yún)⒎謩e為查詢條件和分頁參數(shù)。在Mapper的接口方法中,當有多個入?yún)⒌臅r候建議增加@Param注解,否則就得用param1,param2...來引用參數(shù)。
同時必須在方法上添加注解。查詢使用SelectProvider,插入使用@InsertProvider,更新使用UpdateProvider,刪除使用DeleteProvider。不同的Provider就相當于xml中不同的節(jié)點,如<select>,<insert>,<update>,<delete>。
因為這里是查詢,所以要設(shè)置為SelectProvider,這4個Provider中的參數(shù)都一樣,只有type和method。
type必須設(shè)置為實際執(zhí)行方法的BaseMapperProvider.class(此類在下面),method必須設(shè)置為"dynamicSQL"。
通用Mapper處理的時候會根據(jù)type反射BaseMapperProvider查找方法,而Mybatis的處理機制要求method必須是type類中只有一個入?yún)ⅲ曳祷刂禐镾tring的方法。"dynamicSQL"方法定義在MapperTemplate中,該方法如下:
public String dynamicSQL(Object record) { ? ? return "dynamicSQL"; }
新建的BaseMapperProvider
上面第三點說到了需要一個類繼承MapperTemplate,這是必須的。繼承了它然后再進行相應(yīng)的實現(xiàn),方法名請和Mapper接口中的方法名一致
package com.pinyu.miniprogram.mysql.mappers; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.scripting.xmltags.IfSqlNode; import org.apache.ibatis.scripting.xmltags.MixedSqlNode; import org.apache.ibatis.scripting.xmltags.SqlNode; import org.apache.ibatis.scripting.xmltags.StaticTextSqlNode; import org.apache.ibatis.scripting.xmltags.WhereSqlNode; import tk.mybatis.mapper.entity.EntityColumn; import tk.mybatis.mapper.mapperhelper.EntityHelper; import tk.mybatis.mapper.mapperhelper.MapperHelper; import tk.mybatis.mapper.mapperhelper.MapperTemplate; public class BaseMapperProvider extends MapperTemplate { public BaseMapperProvider(Class<?> mapperClass, MapperHelper mapperHelper) { super(mapperClass, mapperHelper); } public SqlNode selectPage(MappedStatement ms) { Class<?> entityClass = getEntityClass(ms); // 修改返回值類型為實體類型 setResultType(ms, entityClass); List<SqlNode> sqlNodes = new ArrayList<SqlNode>(); // 靜態(tài)的sql部分:select column ... from table sqlNodes.add(new StaticTextSqlNode( "SELECT " + EntityHelper.getSelectColumns(entityClass) + " FROM " + tableName(entityClass))); // 獲取全部列 Set<EntityColumn> columns = EntityHelper.getColumns(entityClass); List<SqlNode> ifNodes = new ArrayList<SqlNode>(); boolean first = true; // 對所有列循環(huán),生成<if test="property!=null">[AND] column = #{property}</if> for (EntityColumn column : columns) { StaticTextSqlNode columnNode = new StaticTextSqlNode( (first ? "" : " AND ") + column.getColumn() + " = #{entity." + column.getProperty() + "} "); if (column.getJavaType().equals(String.class)) { ifNodes.add(new IfSqlNode(columnNode, "entity." + column.getProperty() + " != null and " + "entity." + column.getProperty() + " != '' ")); } else { ifNodes.add(new IfSqlNode(columnNode, "entity." + column.getProperty() + " != null ")); } first = false; } // 將if添加到<where> sqlNodes.add(new WhereSqlNode(ms.getConfiguration(), new MixedSqlNode(ifNodes))); // 處理分頁 sqlNodes.add(new IfSqlNode(new StaticTextSqlNode(" LIMIT #{limit}"), "offset==0")); sqlNodes.add(new IfSqlNode(new StaticTextSqlNode(" LIMIT #{limit} OFFSET #{offset} "), "offset>0")); return new MixedSqlNode(sqlNodes); } }
在這里有一點要求,那就是BaseMapperProvider處理BaseMapper<T>中的方法時,方法名必須一樣,因為這里需要通過反射來獲取對應(yīng)的方法,方法名一致一方面是為了減少開發(fā)人員的配置,另一方面和接口對應(yīng)看起來更清晰。
除了方法名必須一樣外,入?yún)⒈仨毷荕appedStatement ms,除此之外返回值可以是void或者SqlNode之一。
這里先講一下通用Mapper的實現(xiàn)原理。通用Mapper目前是通過攔截器在通用方法第一次執(zhí)行的時候去修改MappedStatement對象的SqlSource屬性。而且只會執(zhí)行一次,以后就和正常的方法沒有任何區(qū)別。
使用Provider注解的這個Mapper方法,Mybatis本身會處理成ProviderSqlSource(一個SqlSource的實現(xiàn)類),由于之前的配置,這個ProviderSqlSource種的SQL是上面代碼中返回的"dynamicSQL"。這個SQL沒有任何作用,如果不做任何修改,執(zhí)行這個代碼肯定會出錯。所以在攔截器中攔截符合要求的接口方法,遇到ProviderSqlSource就通過反射調(diào)用如BaseMapperProvider中的具體代碼去修改原有的SqlSource。
最簡單的處理Mybatis SQL的方法是什么?就是創(chuàng)建SqlNode,使用DynamicSqlSource,這種情況下我們不需要處理入?yún)?,不需要處理代碼中的各種類型的參數(shù)映射。比執(zhí)行SQL的方式容易很多。
有關(guān)這部分的內(nèi)容建議查看通用Mapper的源碼和Mybatis源碼了解,如果不了解在這兒說多了反而會亂。
對上訴實現(xiàn)代碼的描述
首先獲取了實體類型,然后通過setResultType將返回值類型改為entityClass,就相當于resultType=entityClass。
這里為什么要修改呢?因為默認返回值是T,Java并不會自動處理成我們的實體類,默認情況下是Object,對于所有的查詢來說,我們都需要手動設(shè)置返回值類型。
對于insert,update,delete來說,這些操作的返回值都是int,所以不需要修改返回結(jié)果類型。
之后從List<SqlNode> sqlNodes = new ArrayList<SqlNode>();代碼開始拼寫SQL,首先是SELECT查詢頭,在EntityHelper.getSelectColumns(entityClass)中還處理了別名的情況。
然后獲取所有的列,對列循環(huán)創(chuàng)建<if entity.property!=null>column = #{entity.property}</if>節(jié)點。最后把這些if節(jié)點組成的List放到一個<where>節(jié)點中。
這一段使用屬性時用的是 entity. + 屬性名,entity來自哪兒?來自我們前面接口定義處的Param("entity")注解,后面的兩個分頁參數(shù)也是。如果你用過Mybatis,相信你能明白。
之后在<where>節(jié)點后添加分頁參數(shù),當offset==0時和offset>0時的分頁代碼不同。
最后封裝成一個MixedSqlNode返回。
返回后通用Mapper是怎么處理的
這里貼下源碼:
SqlNode sqlNode = (SqlNode) method.invoke(this, ms); DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(ms.getConfiguration(), sqlNode); setSqlSource(ms, dynamicSqlSource);
返回SqlNode后創(chuàng)建了DynamicSqlSource,然后修改了ms原來的SqlSource。
測試:
這里分頁參數(shù)就隨便用了RESTFUL風格隨便寫了下
數(shù)據(jù)庫只有2條數(shù)據(jù)。測試成功
其實有了Mybatis自動生成插件,通用Mapper優(yōu)勢并不是太突出。
非SpringBoot項目的話,原理都是一樣的,只是某些類需要.xml配置文件進行配置和注入
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
java8使用filter()取出自己所需數(shù)據(jù)
這篇文章主要介紹了java8使用filter()取出自己所需數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05mybatis(mybatis-plus)映射文件(XML文件)中特殊字符轉(zhuǎn)義的實現(xiàn)
XML 文件在解析時會將五種特殊字符進行轉(zhuǎn)義,本文主要介紹了mybatis(mybatis-plus)映射文件(XML文件)中特殊字符轉(zhuǎn)義的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2023-12-12基于Redis分布式鎖Redisson及SpringBoot集成Redisson
這篇文章主要介紹了基于Redis分布式鎖Redisson及SpringBoot集成Redisson,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小小伙伴可以參考一下2022-09-09關(guān)于Java JDK安裝、配置環(huán)境變量的問題
這篇文章主要介紹了Java JDK安裝、配置環(huán)境變量,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03IntelliJ IDEA使用maven實現(xiàn)tomcat的熱部署
這篇文章主要介紹了IntelliJ IDEA使用maven實現(xiàn)tomcat的熱部署,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07