springboot實戰之ORM整合(mybatis篇)
- 2019 年 10 月 7 日
- 筆記
前言
本文會介紹一下springboot與mybatis、mybatisplus如何進行整合,文章篇幅會有點長
什麼是MyBatis
MyBatis 是支援訂製化 SQL、存儲過程以及高級映射的優秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設置參數以及獲取結果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或註解,將介面和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成資料庫中的記錄。
MyBatis的功能架構
我們把Mybatis的功能架構分為三層:
API介面層:提供給外部使用的介面API,開發人員通過這些本地API來操縱資料庫。介面層一接收到調用請求就會調用數據處理層來完成具體的數據處理。
數據處理層:負責具體的SQL查找、SQL解析、SQL執行和執行結果映射處理等。它主要的目的是根據調用的請求完成一次資料庫操作。
基礎支撐層:負責最基礎的功能支撐,包括連接管理、事務管理、配置載入和快取處理,這些都是共用的東西,將他們抽取出來作為最基礎的組件。為上層的數據處理層提供最基礎的支撐。
MyBatis的優缺點
優點:
- 簡單易學:本身就很小且簡單。沒有任何第三方依賴,最簡單安裝只要兩個jar文件+配置幾個sql映射文件易於學習,易於使用,通過文檔和源程式碼,可以比較完全的掌握它的設計思路和實現。
- 靈活:mybatis不會對應用程式或者資料庫的現有設計強加任何影響。sql寫在xml里,便於統一管理和優化。通過sql基本上可以實現我們不使用數據訪問框架可以實現的所有功能,或許更多。
- 解除sql與程式程式碼的耦合:通過提供DAL層,將業務邏輯和數據訪問邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。sql和程式碼的分離,提高了可維護性。
- 提供映射標籤,支援對象與資料庫的orm欄位關係映射
- 提供對象關係映射標籤,支援對象關係組建維護
- 提供xml標籤,支援編寫動態sql。
缺點:
- 編寫SQL語句時工作量很大,尤其是欄位多、關聯表多時,更是如此。
- SQL語句依賴於資料庫,導致資料庫移植性差,不能更換資料庫。
- 框架還是比較簡陋,功能尚有缺失,雖然簡化了數據綁定程式碼,但是整個底層資料庫查詢實際還是要自己寫的,工作量也比較大,而且不太容易適應快速資料庫修改。
- 二級快取機制不佳
springboot與mybatis整合
1、pom.xml引入jar
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
2、編輯application.yml
spring: datasource: name: druidDataSource type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springboot-learning?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&serverTimezone=UTC username: root password: config.file=classpath:druid.properties filters: stat,log4j,config max-active: 100 initial-size: 1 max-wait: 60000 min-idle: 1 db-type: mysql time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: select 'x' test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-open-prepared-statements: 50 max-pool-prepared-statement-per-connection-size: 20 connection-properties: config.file=classpath:druid.properties # 啟用加密,配置公鑰。 filter: config: enabled: true #mybaits mybatis: mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.github.lybgeek.orm.mybatis.model configuration: map-underscore-to-camel-case: true cache-enabled: false call-setters-on-nulls: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3、集成程式碼生成器
通過mybatis-generator-maven-plugin和generatorConfig.xml配合自動生成model、dao、mapper.xml模板程式碼
a、在pom.xml引入mybatis-generator-maven-plugin插件
<build> <plugins> <!-- mybaitis 自動生成程式碼配置 mybatis-generator:generate --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.5</version> <executions> <execution> <id>Generate MyBatis Files</id> <goals> <goal>generate</goal> </goals> <phase>generate</phase> <configuration> <verbose>true</verbose> <overwrite>true</overwrite> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.44</version> </dependency> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.5</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> </dependencies> </plugin> </plugins> </build>
b、在classpath下引入generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!--<properties resource="jdbc.properties" />--> <properties url="file:///F:/jdbc.properties" /> <context id="mysqlTables" targetRuntime="MyBatis3"> <commentGenerator> <!-- 是否去除自動生成的注釋 true:是 : false:否 --> <property name="suppressAllComments" value="true" /> </commentGenerator> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="${jdbc.url}" userId="${jdbc.username}" password="${jdbc.password}" /> <!-- 指定生成的類型為java類型,避免資料庫中number等類型欄位 --> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!-- 自動生成的實體的存放包路徑 --> <javaModelGenerator targetPackage="com.github.lybgeek.orm.mybatis.model" targetProject="src/main/java"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!--自動生成的*Mapper.xml文件存放路徑 --> <sqlMapGenerator targetPackage="mapper/bookorderitem" targetProject="src/main/resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!--自動生成的*Mapper.java存放路徑 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.github.lybgeek.orm.mybatis.dao" targetProject="src/main/java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <table tableName="book_order_item" domainObjectName="BookOrderItem" enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="true"> <generatedKey column="id" sqlStatement="mysql" identity="true" /> </table> </context> </generatorConfiguration>
4、在啟動類上加上@MapperScan註解
@SpringBootApplication @MapperScan(basePackages = {"com.github.lybgeek.orm.mybatis.dao"}) public class OrmApplication { public static void main( String[] args ) { SpringApplication.run(OrmApplication.class,args); } }
mybatis其他一些擴展
分頁
1、pom引入
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> </dependency>
2、分頁程式碼例子
@Override public PageResult<BookOrderDTO> pageBookOrder(PageQuery<BookOrderDTO> pageQuery) { Integer pageNo = ObjectUtils.isEmpty(pageQuery.getPageNo()) ? 1 : pageQuery.getPageNo(); Integer pageSize = ObjectUtils.isEmpty(pageQuery.getPageSize()) ? 5 : pageQuery.getPageSize(); BookOrderDTO bookOrderDTO = pageQuery.getQueryParams(); Page<BookOrder> page = PageHelper.startPage(pageNo,pageSize); PageHelper.orderBy("bo.create_date DESC"); List<BookOrderDTO> list = listBookOrders(bookOrderDTO); return PageUtil.INSTANCE.getPage(page,list); }
通過攔截器擴展,實現欄位填充功能
以自動生成資料庫創建時間和更新時間為例
1、編寫自定義註解
Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) @Documented public @interface CreateDate { String value() default ""; } @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) @Documented public @interface UpdateDate { String value() default ""; }
2、編寫攔截器
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) public class DateTimeInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; // 獲取 SQL SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); // 獲取參數 Object parameter = invocation.getArgs()[1]; // 獲取私有成員變數 Field[] declaredFields = parameter.getClass().getDeclaredFields(); // Class parentClz = parameter.getClass().getSuperclass(); // // if(parentClz.newInstance() instanceof BaseEntity){ // declaredFields = parentClz.getDeclaredFields(); // } for (Field field : declaredFields) { if (field.getAnnotation(CreateDate.class) != null) { if (SqlCommandType.INSERT.equals(sqlCommandType)) { // insert語句插入createDate field.setAccessible(true); field.set(parameter, new Date()); } } else if (field.getAnnotation(UpdateDate.class) != null) { if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { // insert 或update語句插入updateDate field.setAccessible(true); field.set(parameter, new Date()); } } } return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }
3、配置攔截器
@Bean public ConfigurationCustomizer configurationCustomizer() { return configuration -> configuration.addInterceptor(dateTimeInterceptor()); } @Bean public DateTimeInterceptor dateTimeInterceptor() { return new DateTimeInterceptor(); }
4、在需要填充欄位上,加上指定註解
@CreateDate private Date createDate; @UpdateDate private Date updateDate;
什麼是MyBatis-Plus
MyBatis-Plus(簡稱 MP)是一個 MyBatis 的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。
MyBatis-Plus的特性
- 無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
- 損耗小:啟動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作
- 強大的 CRUD 操作:內置通用 Mapper、通用 Service,僅僅通過少量配置即可實現單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求
- 支援 Lambda 形式調用:通過 Lambda 表達式,方便的編寫各類查詢條件,無需再擔心欄位寫錯
- 支援主鍵自動生成:支援多達 4 種主鍵策略(內含分散式唯一 ID 生成器 – Sequence),可自由配置,完美解決主鍵問題
- 支援 ActiveRecord 模式:支援 ActiveRecord 形式調用,實體類只需繼承 Model 類即可進行強大的 CRUD 操作
- 支援自定義全局通用操作:支援全局通用方法注入( Write once, use anywhere )
- 內置程式碼生成器:採用程式碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層程式碼,支援模板引擎,更有超多自定義配置等您來使用
- 內置分頁插件:基於 MyBatis 物理分頁,開發者無需關心具體操作,配置好插件之後,寫分頁等同於普通 List 查詢
- 分頁插件支援多種資料庫:支援 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多種資料庫
- 內置性能分析插件:可輸出 Sql 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢
- 內置全局攔截插件:提供全表 delete 、 update 操作智慧分析阻斷,也可自定義攔截規則,預防誤操作
springboot與mybatis-plus整合
1、pom引入
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
2、編輯application.yml
spring: datasource: name: druidDataSource type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springboot-learning?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&serverTimezone=UTC username: root password: config.file=classpath:druid.properties filters: stat,log4j,config max-active: 100 initial-size: 1 max-wait: 60000 min-idle: 1 db-type: mysql time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: select 'x' test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-open-prepared-statements: 50 max-pool-prepared-statement-per-connection-size: 20 connection-properties: config.file=classpath:druid.properties # 啟用加密,配置公鑰。 filter: config: enabled: true mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml #實體掃描,多個package用逗號或者分號分隔 typeAliasesPackage: com.github.lybgeek.orm.mybatisplus.model global-config: #主鍵類型 0:"資料庫ID自增", 1:"用戶輸入ID",2:"全局唯一ID (數字類型唯一ID)", 3:"全局唯一ID UUID"; id-type: 0 #欄位策略 0:"忽略判斷",1:"非 NULL 判斷"),2:"非空判斷" field-strategy: 1 #駝峰下劃線轉換 db-column-underline: true #刷新mapper 調試神器 refresh-mapper: true #資料庫大寫下劃線轉換 #capital-mode: true # Sequence序列介面實現類配置 #key-generator: com.baomidou.mybatisplus.incrementer.OracleKeyGenerator #邏輯刪除配置 logic-delete-value: -1 logic-not-delete-value: 0 #自定義填充策略介面實現 #meta-object-handler: com.baomidou.springboot.xxx configuration: map-underscore-to-camel-case: true cache-enabled: false call-setters-on-nulls: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3、集成程式碼生成器
a、pom.xml引入
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> </dependency>
b、程式碼生成器核心程式碼
// 執行 main 方法控制台輸入模組表名回車自動生成對應項目目錄中 public class CodeGenerator { /** * <p> * 讀取控制台內容 * </p> */ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("請輸入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotEmpty(ipt)) { return ipt; } } throw new MybatisPlusException("請輸入正確的" + tip + "!"); } public static void main(String[] args) throws Exception{ // 程式碼生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String basePath = CodeGenerator.class.getResource("").getPath(); String projectPath = basePath.substring(0, basePath.indexOf("/target")); // String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("lyb-geek"); gc.setOpen(false); gc.setBaseColumnList(true); gc.setBaseResultMap(true); gc.setServiceName("%sService"); // gc.setSwagger2(true);// 實體屬性 Swagger2 註解 gc.setDateType(DateType.ONLY_DATE); mpg.setGlobalConfig(gc); String url = YmlUtil.getValue("spring.datasource.druid.url").toString(); String username = YmlUtil.getValue("spring.datasource.druid.username").toString(); String pwd = PropertiesUtil.INSTANCE.getProperty("password"); String publicKey = PropertiesUtil.INSTANCE.getProperty("config.decrypt.key"); String password = ConfigTools.decrypt(publicKey,pwd); // 數據源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl(url); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername(username); dsc.setPassword(password); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName(scanner("模組名")); pc.setParent("com.github.lybgeek.orm"); pc.setEntity("model"); pc.setMapper("dao"); mpg.setPackageInfo(pc); // 自定義配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 freemarker String templatePath = "/templates/mapper.xml.ftl"; // 如果模板引擎是 velocity // String templatePath = "/templates/mapper.xml.vm"; // 自定義輸出配置 List<FileOutConfig> focList = new ArrayList<>(); // 自定義配置會被優先輸出 // focList.add(new FileOutConfig(templatePath) { // @Override // public String outputFile(TableInfo tableInfo) { // // 自定義輸出文件名 , 如果你 Entity 設置了前後綴、此處注意 xml 的名稱會跟著發生變化!! // return projectPath + "/src/main/resources/mapperPlus/" + pc.getModuleName() // + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; // } // }); focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // 自定義輸出文件名 , 如果你 Entity 設置了前後綴、此處注意 xml 的名稱會跟著發生變化!! return projectPath + "/src/main/resources/mapper/mybatisplus/" + tableInfo.getEntityName().toLowerCase() + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); /* cfg.setFileCreate(new IFileCreate() { @Override public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { // 判斷自定義文件夾是否需要創建 checkDir("調用默認方法創建的目錄"); return false; } }); */ cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); // 配置自定義輸出模板 //指定自定義模板路徑,注意不要帶上.ftl/.vm, 會根據使用的模板引擎自動識別 // templateConfig.setEntity("templates/entity2.java"); // templateConfig.setService(); // templateConfig.setController(); templateConfig.setXml(null); mpg.setTemplate(templateConfig); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setSuperEntityClass("com.github.lybgeek.orm.common.model.BaseEntity"); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); // strategy.setSuperControllerClass("com.github.lybgeek.orm.controller.BaseController"); strategy.setInclude(scanner("表名,多個英文逗號分割").split(",")); strategy.setSuperEntityColumns("id","create_date","update_date"); strategy.setControllerMappingHyphenStyle(true); // strategy.setTablePrefix(pc.getModuleName() + "_"); //移除表的前綴 // strategy.setTablePrefix("t_"); mpg.setStrategy(strategy); mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); } }
4、與mybatis一樣,在啟動類上加上@MapperScan註解
總結
本來想分為兩篇分別介紹mybatis和mybatis-plus,但後邊覺得mybatis-plus官網的例子已經很詳盡了,就沒必要多花篇幅介紹,在這邊安利一下mybatis-plus,它確實是一個很強大而且易用的orm框架,而且是國人開發的,比較了解國人開發的一些痛點,而且集成了分散式事務,但是暫時支援rabbit實現可靠消息分散式事務3.1.1 以上版本,不過這個方案是否能使用在生產線上,有待驗證。其官網鏈接如下
https://mp.baomidou.com/
參考文檔
https://www.w3cschool.cn/mybatis/ https://mp.baomidou.com/
demo鏈接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-orm