SpringBoot31 整合SpringJDBC、整合MyBatis、利用AOP實現多數據源

 

1、整合SpringJDBC

1  JDBC

  JDBC(Java Data Base Connectivity,Java 數據庫鏈接)是一種用於執行 SQL 語句的 Java API,能夠爲多種關係數據庫提供統一訪問,它一組用 Java 語言編寫的類和接口組成。JDBC 提供了一種基準,據此能夠構建更高級的工具和接口,使數據庫開發人員可以編寫數據庫應用程序。html

  1.1 優勢

    JDBC 就是一套 Java 訪問數據庫的 API 規範,利用這套規範屏蔽了各類數據庫 API 調用的差別性;java

    當 Java 程序須要訪問數據庫時,直接調用 JDBC API 相關代碼進行操做,JDBC 調用各種數據庫的驅動包進行交互,最後數據庫驅動包和對應的數據庫通信,完成 Java 程序操做數據庫。mysql

  1.2 缺點

    直接在 Java 程序中使用 JDBC 比較複雜,須要 7 步才能完成數據庫的操做:git

      加載數據庫驅動 -> 創建數據庫鏈接 -> 建立數據庫操做對象 -> 編寫SQL語句 -> 利用數據庫操做對象執行數據庫操做 -> 獲取並操做結果集 -> 關閉鏈接對象和操做對象,回收資源web

    利用JDBC操做數據庫參考博文spring

  1.3 新技術

    因爲JDBC操做數據庫很是複雜,因此牛人們編寫了不少ORM框架,其中Hibernate、Mybatis、SpringJDBC最流行;sql

    時間又過了N年,數據庫

    又有牛人在Hibernate的基礎上開發了SpringDataJPA,在Mybatis的基礎上開發出了MybatisPlus。apache

 

2 SpringBoot集成SpringJDBC環境搭建

  2.1 建立一個SpringBoot項目

    引入 spring-boot-starter-web 、spring-boot-starter-jdbc、mysql-connector-java 者三個主要依賴;編程

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
必要依賴

    再引入 devtools、lombok這兩個輔助依賴。

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
輔助依賴

    2.1.1 依賴說明   

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xunyji</groupId>
    <artifactId>spring_jdbc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring_jdbc</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>
pom.xml

      查看pom.xml依賴圖能夠知道 spring-boot-starter-jdbc 依賴了spring-jdbc、HikariCP;

      spring-jdbc主要提供JDBC操做相關的接口,HikariCP就是傳說中最快的鏈接池。

  2.2 數據庫準備

    2.2.1 建立數據表

DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id', `name` varchar(32) DEFAULT NULL COMMENT '用戶名', `password` varchar(32) DEFAULT NULL COMMENT '密碼', `age` int DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
users

    2.2.1 配置數據源信息

      坑:Spring Boot 2.1.0 中,com.mysql.jdbc.Driver 已通過期,推薦使用 com.mysql.cj.jdbc.Driver。

      技巧:IDEA是能夠鏈接數據庫的喲,並且還能夠反向生成對應的實體類喲;IDEA鏈接數據庫並生成實體列參考博文

spring.datasource.url=jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root spring.datasource.password=**** spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

    2.2.3 建立實體類

      版案例使用了lombok進行簡化編寫

package com.xunyji.spring_jdbc.model.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * @author 王楊帥 * @create 2018-11-18 10:43 * @desc **/ @Data // 自動生成get、set、toString、equals、hashCode、canEaual方法 和 顯示無參構造器
@Builder // 生成builder方法
@NoArgsConstructor // 生成無參構造器
@AllArgsConstructor // 自動生成全部字段的有參構造器,會覆蓋無參構造器
public class User { private Long id; private String name; private Integer age; private String email; }
User.java

  2.3 編寫持久層

    2.3.1 持久層接口

package com.xunyji.spring_jdbc.repository; import com.xunyji.spring_jdbc.model.entity.User; import org.springframework.stereotype.Repository; import java.util.List; /** * @author 王楊帥 * @create 2018-11-18 10:53 * @desc user表對應的持久層接口 **/
public interface UserRepository { Integer save(User user); Integer update(User user); Integer delete(Integer id); List<User> findAll(); User findById(Integer id); }
UserRepository.java

    2.3.2 持久層實現類

      技巧:在實現類中依賴注入 JdbcTemplate,它是Spring提供的用於JDBC操做的工具類。

 @Autowired private JdbcTemplate jdbcTemplate;
package com.xunyji.spring_jdbc.repository.impl; import com.xunyji.spring_jdbc.comm.exception.ExceptionEnum; import com.xunyji.spring_jdbc.comm.exception.FuryException; import com.xunyji.spring_jdbc.model.entity.User; import com.xunyji.spring_jdbc.repository.UserRepository; import com.xunyji.spring_jdbc.repository.impl.resultmap.UserRowMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.util.List; /** * @author 王楊帥 * @create 2018-11-18 10:55 * @desc user表對應的持久層實現類 **/ @Repository public class UserRepositoryImpl implements UserRepository { private Logger log = LoggerFactory.getLogger(this.getClass()); @Autowired private JdbcTemplate jdbcTemplate; @Override public Boolean save(User user) { Integer saveResult = jdbcTemplate.update( "INSERT user (name, age, email) VALUES (?, ?, ?)", user.getName(), user.getAge(), user.getEmail() ); if (saveResult.equals(1)) { return true; } return false; } @Override public Boolean update(User user) throws Exception { Integer updateResult = jdbcTemplate.update( "UPDATE user SET name = ?, age = ?, email = ? WHERE id = ?", user.getName(), user.getAge(), user.getEmail(), user.getId() ); if (updateResult.equals(1)) { return true; } return false; } @Override public Boolean delete(Long id) { Integer deleteResult = jdbcTemplate.update( "DELETE FROM user WHERE id = ?", id ); if (deleteResult.equals(1)) { return true; } return false; } @Override public List<User> findAll() { return jdbcTemplate.query( "SELECT * FROM user", new UserRowMapper() ); } @Override public User findById(Long id) { try { User user = jdbcTemplate.queryForObject( "SELECT id, name, age, email FROM user WHERE id = ?", new Object[]{id}, new BeanPropertyRowMapper<>(User.class) ); return user; } catch (EmptyResultDataAccessException e) { log.info(e.getMessage()); throw new FuryException(ExceptionEnum.RESULT_IS_EMPTY); } } }
UserRepositoryImpl .java

      技巧:新增、更新、刪除都是利用JdbcTemplate的update方法,update方法的返回值是執行成功的記錄數(需在實現類中根據結果判斷是否操做成功);

      技巧:利用JdbcTemplate的queryForObject方法查詢單條記錄,利用JdbcTemplate的query查詢多條記錄;

      技巧:利用JdbcTemplate的queryForObject方法查詢單條記錄時若是查詢不到就會拋出EmptyResultDataAccessException,須要進行捕獲。

      技巧:查詢記錄時須要對結果集進行封裝,能夠直接利用BeanPropertyRowMapper實例進行封裝,或者自定義一個實現了UserRowMapper的實現類

    2.3.3 持久層測試類

package com.xunyji.spring_jdbc.repository.impl; import com.xunyji.spring_jdbc.model.entity.User; import com.xunyji.spring_jdbc.repository.UserRepository; import lombok.extern.slf4j.Slf4j; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class UserRepositoryImplTest { @Autowired private UserRepository userRepository; private User user; @Before public void init() { user = User.builder() .id(81L) .age(33) .name("warrior") .email("cqdzwys@163.com") .build(); } @Test public void save() throws Exception { Boolean saveNumber = userRepository.save(user); log.info("新增結果爲:" + saveNumber); } @Test public void update() throws Exception { Boolean updateResult = userRepository.update(user); log.info("更新結果爲:" + updateResult); } @Test public void delete() throws Exception { Boolean delete = userRepository.delete(81L); log.info("刪除結果爲:" + delete); } @Test public void findById() throws Exception { User byId = userRepository.findById(8L); log.info("獲取的數據信息爲:" + byId); } @Test public void findAll() throws Exception { List<User> all = userRepository.findAll(); log.info("獲取到的列表數據爲:" + all); all.stream().forEach(System.out::println); } }
UserRepositoryImplTest.java

  2.4 編寫服務層

    待更新...

  2.5 編寫控制層

    待更新...

3 多數據源

  3.1 硬編碼實現【不推薦】

    思路:配置多個數據源 -> 經過配置讀取兩個數據源 -> 利用讀取到數據源分別建立各自的JdbcTemplate對應的Bean並交給Spring容器管理 -> 在調用持久層中的方法時動態傳入JdbcTemplate實例

    3.1.1 配置多個數據源

      技巧:SpringBoot2.x 默認的數據庫驅動爲  com.mysql.cj.jdbc.Driver,默認的數據庫鏈接池爲 HikariCP 

      技巧:HikariCP 鏈接池讀取數據源url時是經過 jdbc-url 獲取的

spring: datasource: primary: jdbc-url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
 username: root password: 182838 driver-class-name: com.mysql.cj.jdbc.Driver secondary: jdbc-url: jdbc:mysql://localhost:3306/testdemo2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
 username: root password: 182838 driver-class-name: com.mysql.cj.jdbc.Driver
application.yml

    3.1.2 讀取配置的數據源

      技巧:ConfigurationProperties用於方法上時會自動讀取配置文件中的值並設置到該方法的返回對象上

      技巧:@Primary 註解的做用是當依賴注入有多個類型相同的Bean時,添加了@Primary的那個Bean會默認被注入

    3.1.3 建立JdbcTemplate

    3.1.4 配置類代碼彙總

package com.xunyji.spring_jdbc_multi_datasource01.comm.datasource; import com.xunyji.spring_jdbc_multi_datasource01.model.entity.User; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; /** * @author 王楊帥 * @create 2018-11-18 16:17 * @desc 多數據源配置 **/ @Configuration @Slf4j public class DataSourceConfig { @Bean @Primary @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } @Bean public JdbcTemplate primaryJdbcTemplate( @Qualifier(value = "primaryDataSource") DataSource dataSource ) { return new JdbcTemplate(dataSource); } @Bean public JdbcTemplate secondaryJdbcTemplate( @Qualifier(value = "secondaryDataSource") DataSource dataSource ) { return new JdbcTemplate(dataSource); } }
DataSourceConfig.java

    3.1.5 缺點

      這種硬編碼方式實現多數據源時,須要在調用持久層方法時指定動態的JdbcTemplate

  3.2 AOP實現【推薦】

    思路:配置多個數據源 -> 讀取數據源信息 -> 利用AbstractRoutingDataSource抽象類配置數據源信息 -> 利用AOP實現動態數據源切換

    3.2.1 AOP知識點

      參考博文

    3.2.2 配置多個數據源

spring: datasource: primary: jdbc-url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
 username: root password: 182838 driver-class-name: com.mysql.cj.jdbc.Driver secondary: jdbc-url: jdbc:mysql://localhost:3306/testdemo2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
 username: root password: 182838 driver-class-name: com.mysql.cj.jdbc.Driver
application.yml

    3.2.3 建立AbstractRoutingDataSource實現類

package com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.lang.Nullable; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @author 王楊帥 * @create 2018-11-18 21:18 * @desc 動態數據源 **/
public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public DynamicDataSource( DataSource defaultTargetDataSource, Map<String, DataSource> targetDataSources ) { // 01 經過父類設置默認數據源
 super.setDefaultTargetDataSource(defaultTargetDataSource); // 02 經過父類設置數據源集合
        super.setTargetDataSources(new HashMap<>(targetDataSources)); // 03 經過父類對數據源進行解析 https://blog.csdn.net/u011463444/article/details/72842500
 super.afterPropertiesSet(); } @Nullable @Override protected Object determineCurrentLookupKey() { // 獲取數據源,若是沒有指定,則爲默認數據源
        return getDataSource(); } /** * 設置數據源 * @param dataSource */
    public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } /** * 獲取數據源 * @return */
    public static String getDataSource() { return contextHolder.get(); } /** * 清除數據源 */
    public static void clearDataSource() { contextHolder.remove(); } }
DynamicDataSource.java

    3.2.4 讀取數據源並配置動態數據源

package com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; import javax.xml.crypto.Data; import java.util.HashMap; import java.util.Map; /** * @author 王楊帥 * @create 2018-11-18 21:42 * @desc 動態數據源讀取與配置 **/ @Configuration public class DynamicDataSourceConfig { /** * 讀取並配置數據源1 * @return */ @Bean @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } /** * 讀取並配置數據源2 * @return */ @Bean @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } /** * 根據數據源1和數據源2配置動態數據源 * @param primaryDataSource * @param secondaryDataSource * @return */ @Bean @Primary public DynamicDataSource dataSource( DataSource primaryDataSource, DataSource secondaryDataSource ) { Map<String, DataSource> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceNames.FIRST, primaryDataSource); targetDataSources.put(DataSourceNames.SECOND, secondaryDataSource); return new DynamicDataSource(primaryDataSource, targetDataSources); } }
DynamicDataSourceConfig.java

    3.2.5 建立指定數據源的註解

package com.xunyji.spring_jdbc_datasource.comm.datasource; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { String name() default ""; }
DataSource.java

    3.2.6 建立常量接口

package com.xunyji.spring_jdbc_datasource.comm.datasource; /** * @author 王楊帥 * @create 2018-12-19 22:28 * @desc **/
public interface DataSourceNames { String FIRST = "first"; String SECOND = "second"; }
DataSourceNames.java

    3.2.7 建立切換數據源的AOP

package com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * @author 王楊帥 * @create 2018-11-18 21:53 * @desc 數據源切換AOP **/ @Aspect @Component @Slf4j public class DataSourceAspect implements Ordered{ @Pointcut("@annotation(com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce.DataSource)") public void dataSourcePointCut() {} @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); DataSource ds = method.getAnnotation(DataSource.class); if(ds == null){ DynamicDataSource.setDataSource(DataSourceNames.FIRST); log.debug("set datasource is " + DataSourceNames.FIRST); }else { DynamicDataSource.setDataSource(ds.name()); log.debug("set datasource is " + ds.name()); } try { return point.proceed(); } finally { DynamicDataSource.clearDataSource(); log.debug("clean datasource"); } } @Override public int getOrder() { return 1; } }
DataSourceAspect.java

    3.2.8 常見錯誤

      錯區1:因爲利用AOP實現的所數據源,因此須要額外導入Aspect相關的依賴

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>

      錯誤2:啓動應用後會出現循環應用錯誤,錯誤信息以下

      緣由:SpringBoot默認對數據源進行了配置,若是想要動態數據源生效就必須關閉數據源的自動配置

      解決:在啓動類的註解中排除掉數據源自動配置類便可

     3.2.9 知識點複習

      》ThreadLocal原理與應用場景

      》AbstractRoutingDataSource原理解析

      

2、整合MyBatis

1 ORM框架

(摘自:https://gitbook.cn/gitchat/column/5b86228ce15aa17d68b5b55a/topic/5be8f0552c33167c317c6a7f)

  1.1 概念

    對象關係映射(Object Relational Mapping,ORM)模式是一種爲了解決面向對象與關係數據庫存在的互不匹配的現象的技術。簡單的說,ORM 是經過使用描述對象和數據庫之間映射的元數據,將程序中的對象自動持久化到關係數據庫中。

  1.2 爲何須要ORM

    當你開發一個應用程序的時候(不使用 O/R Mapping),可能會寫很多數據訪問層代碼,用來從數據庫保存、刪除、讀取對象信息等;在 DAL 中寫了不少的方法來讀取對象數據、改變狀態對象等任務,而這些代碼寫起來老是重複的。針對這些問題 ORM 提供瞭解決方案,簡化了將程序中的對象持久化到關係數據庫中的操做。

    ORM 框架的本質是簡化編程中操做數據庫的編碼,在 Java 領域發展到如今基本上就剩兩家最爲流行,一個是宣稱能夠不用寫一句 SQL 的 Hibernate,一個是以動態 SQL 見長的 MyBatis,二者各有特色。在企業級系統開發中能夠根據需求靈活使用,會發現一個有趣的現象:傳統企業大都喜歡使用 Hibernate,而互聯網行業一般使用 MyBatis。

  

2 MyBatis介紹

(摘自:https://gitbook.cn/gitchat/column/5b86228ce15aa17d68b5b55a/topic/5be8f0552c33167c317c6a7f)

  2.1 概念

    MyBatis 支持普通的 SQL 查詢,存儲過程和高級映射的優秀持久層框架。MyBatis 消除了幾乎全部的 JDBC 代碼和參數的手工設置以及對結果集的檢索封裝。MaBatis 可使用簡單的 XML 或註解用於配置和原始映射,將接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 對象)映射成數據庫中的記錄。

  2.2 優勢

    SQL 被統一提取出來,便於統一管理和優化
    SQL 和代碼解耦,將業務邏輯和數據訪問邏輯分離,使系統的設計更清晰、更易維護、更易單元測試
    提供映射標籤,支持對象與數據庫的 ORM 字段關係映射
    提供對象關係映射標籤,支持對象關係組件維護
    靈活書寫動態 SQL,支持各類條件來動態生成不一樣的 SQL

  2.3 缺點   

    編寫 SQL 語句時工做量很大,尤爲是字段多、關聯表多時,更是如此
    SQL 語句依賴於數據庫,致使數據庫移植性差

 

3 mybatis原理解析

  待更新2018年12月27日10:54:46

 

4 SpringBoot整合Mybatis(xml版本)

  4.1 引入依賴

    引入web啓動依賴和mysql、mybatis相關依賴

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xunyji</groupId>
    <artifactId>mybatis_xml_datasource</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mybatis_xml_datasource</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
pom.xml

  4.2 規範項目路徑

    4.2.1 路徑說明

      mapper接口:就是通常的持久層接口而已,通常放在repository包下;mapper接口位於程序源文件目錄

      mapper文件:就是書寫sql語句的文件,每一個mapper映射文件都和一個mapper接口一一對應;mapper映射文件和mybatis的配置文件都位於resources目錄

      mybatis配置文件:就是mybatis的一些基本配置信息;mybatis配置文件通常和存放mappr映射文件的目錄處於一個目錄下【本案例處於resouces目錄下的mybatis目錄下】

      路徑配置:因爲mybatis官方提供的啓動包提供了一個配置類來配置mapper映射文件和配置文件的路徑【org.mybatis.spring.boot.autoconfigure.MybatisProperties】

      項目結構以下所示:

    4.2.2 配置類

      配置類:org.mybatis.spring.boot.autoconfigure.MybatisProperties,可配置的選項和配置實例以下所示

  4.3 數據源配置

    在application.yml文件中配置基本數據源

spring: datasource: url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
 username: root password: 182838 driver-class-name: com.mysql.cj.jdbc.Driver

  4.4 mybatis配置文件

    mybatis配置文件位於resouces目錄下的mybatis文件夾下,須要在application.yml中指定mybatis配置文件的位置

<?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>
    <typeAliases>
        <typeAlias alias="Integer" type="java.lang.Integer" />
        <typeAlias alias="Long" type="java.lang.Long" />
        <typeAlias alias="HashMap" type="java.util.HashMap" />
        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
        <typeAlias alias="ArrayList" type="java.util.ArrayList" />
        <typeAlias alias="LinkedList" type="java.util.LinkedList" />
    </typeAliases>
</configuration>

  4.5 數據表和實體類

    在數據源對應的數據庫中建立一張users表

/* Navicat MySQL Data Transfer Source Server : mysql5.4 Source Server Version : 50540 Source Host : localhost:3306 Source Database : testdemo Target Server Type : MYSQL Target Server Version : 50540 File Encoding : 65001 Date: 2018-12-25 15:16:15 */ SET FOREIGN_KEY_CHECKS=0; -- ----------------------------
-- Table structure for `users` -- ---------------------------- DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id', `userName` varchar(32) DEFAULT NULL COMMENT '用戶名', `passWord` varchar(32) DEFAULT NULL COMMENT '密碼', `user_sex` varchar(32) DEFAULT NULL, `nick_name` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8; -- ----------------------------
-- Records of users -- ---------------------------- INSERT INTO `users` VALUES ('1', 'assassin', '阿斯蒂芬', 'MAN', '暗室逢燈'); INSERT INTO `users` VALUES ('28', 'aa', 'a123456', 'MAN', null); INSERT INTO `users` VALUES ('29', 'bb', 'b123456', 'WOMAN', null); INSERT INTO `users` VALUES ('30', 'cc', '0134123', 'WOMAN', null); INSERT INTO `users` VALUES ('31', 'aa', 'a123456', 'MAN', null); INSERT INTO `users` VALUES ('32', 'bb', 'b123456', 'WOMAN', null); INSERT INTO `users` VALUES ('33', 'cc', 'b123456', 'WOMAN', null); INSERT INTO `users` VALUES ('34', null, '123321', null, null); INSERT INTO `users` VALUES ('35', null, '123321', 'MAN', null); INSERT INTO `users` VALUES ('36', null, '123321', 'MAN', null); INSERT INTO `users` VALUES ('37', '王楊帥', '1234', null, null); INSERT INTO `users` VALUES ('38', '楊玉林', null, 'MAN', null);
users

    在項目中建立一個實體類類User和數據源中的users表對應

    技巧01:能夠利用IDEA直接生成,參考文檔

    注意:User實體類中用到了一個枚舉

package com.xunyji.mybatis_xml.model.enums; /** * @author 王楊帥 * @create 2018-12-25 14:17 * @desc **/
public enum UserSexEnum { WOMAN, MAN }
UserSexEnum.java
package com.xunyji.mybatis_xml.model.entity; import com.xunyji.mybatis_xml.model.enums.UserSexEnum; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * @author 王楊帥 * @create 2018-12-25 14:18 * @desc **/ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class User { private Long id; private String userName; private String passWord; private UserSexEnum userSex; private String nickName; }
User.java

  4.6 建立mapper接口

    技巧01:跟通常的接口同樣,須要在接口上標註@Repository註解

package com.xunyji.mybatis_xml.repository; import com.xunyji.mybatis_xml.model.entity.User; import com.xunyji.mybatis_xml.model.param.UserParam; import org.springframework.stereotype.Repository; import java.util.List; /** * @author 王楊帥 * @create 2018-12-23 11:31 * @desc **/ @Repository public interface UserRepository { List<User> getAll(); User getOne(Long id); void insert(User user); void update(User user); void delete(Long id); List<User> getList(UserParam userParam); Integer getCount(UserParam userParam); }
UserRepository.java

  4.7 建立mapper映射文件

    技巧01:mapper映射文件位於resources目錄下,並且須要在application.yml中配置mapper映射文件的位置

<?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.xunyji.mybatis_xml.repository.UserRepository" >

    <!--表結構和實體類的映射關係 start-->
    <resultMap id="BaseResultMap" type="com.xunyji.mybatis_xml.model.entity.User" >
        <id column="id" property="id" jdbcType="BIGINT" />
        <result column="userName" property="userName" jdbcType="VARCHAR" />
        <result column="passWord" property="passWord" jdbcType="VARCHAR" />
        <result column="user_sex" property="userSex" jdbcType="VARCHAR" javaType="com.xunyji.mybatis_xml.model.enums.UserSexEnum"/>
        <result column="nick_name" property="nickName" jdbcType="VARCHAR" />
    </resultMap>
    <!--表結構和實體類的映射關係 end-->

    <sql id="Base_Column_List" > id, userName, passWord, user_sex, nick_name </sql>

    <sql id="Base_Where_List">
        <if test="userName != null and userName != ''"> and userName = #{userName} </if>
        <if test="userSex != null and userSex != ''"> and user_sex = #{userSex} </if>
    </sql>

    <!--查詢全部數據 start-->
    <select id="getAll" resultMap="BaseResultMap"  > SELECT <include refid="Base_Column_List" /> FROM users </select>
    <!--查詢全部數據 end-->

    <!--分頁查詢 start-->
    <select id="getList" resultMap="BaseResultMap" parameterType="com.xunyji.mybatis_xml.model.param.UserParam">
        select
        <include refid="Base_Column_List" />
        from users where 1=1
        <include refid="Base_Where_List" /> order by id ASC limit #{beginLine} , #{pageSize} </select>
    <!--分頁查詢 end-->

    <!--記錄總數 start-->
    <select id="getCount" resultType="Integer" parameterType="com.xunyji.mybatis_xml.model.param.UserParam">
        select count(1) from users where 1 = 1
        <include refid="Base_Where_List" />
    </select>
    <!--記錄總數-->

    <!--根據ID獲取數據 start-->
    <select id="getOne" parameterType="Long" resultMap="BaseResultMap" > SELECT <include refid="Base_Column_List" /> FROM users WHERE id = #{id} </select>
    <!--根據ID獲取數據 end-->

    <!--插入數據 start-->
    <insert id="insert" parameterType="com.xunyji.mybatis_xml.model.entity.User" > INSERT INTO users (userName,passWord,user_sex, nick_name) VALUES (#{userName}, #{passWord}, #{userSex}, #{nickName}) </insert>
    <!--插入數據 end-->

    <!--更新數據 start-->
    <update id="update" parameterType="com.xunyji.mybatis_xml.model.entity.User" > UPDATE users SET <if test="userName != null">userName = #{userName},</if>
        <if test="passWord != null">passWord = #{passWord},</if> nick_name = #{nickName} WHERE id = #{id} </update>
    <!--更新數據 end-->

    <!--刪除數據 start-->
    <delete id="delete" parameterType="Long" > DELETE FROM users WHERE id =#{id} </delete>
    <!--刪除數據 end-->

</mapper>
UserMapper.xml

  4.8 必備配置

    4.8.1 啓動類配置

      須要在啓動類上指定mapper接口路徑

    4.8.2 application.yml配置

spring: datasource: url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
 username: root password: 182838 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml type-aliases-package: com.xunyji.mybatis_xml.model
application.yml

  4.9 測試類

package com.xunyji.mybatis_xml.repository; import com.sun.org.apache.xml.internal.security.keys.keyresolver.implementations.X509IssuerSerialResolver; import com.xunyji.mybatis_xml.model.entity.User; import com.xunyji.mybatis_xml.model.enums.UserSexEnum; import com.xunyji.mybatis_xml.model.param.UserParam; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; import java.util.Random; import java.util.stream.Collectors; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class UserRepositoryTest { @Autowired private UserRepository userRepository; @Test public void getAll() throws Exception { List<User> userList = userRepository.getAll(); userList.forEach(System.out::println); } @Test public void getOne() throws Exception { List<User> userList = userRepository.getAll(); log.info("獲取到的user列表爲:" + userList); List<Long> idList = userList.stream() .map(user -> user.getId()) .collect(Collectors.toList()); log.info("獲取到的ID列表爲:" + idList); int index = new Random().nextInt(idList.size()); // 隨機獲取一個idList的索引號
        Long indexValue = idList.get(index); // 獲取隨機索引號index在idList中的對應值
 User user = userRepository.getOne(indexValue); log.info("ID值爲{}的用戶信息爲:{}", indexValue, user); } @Test public void insert() throws Exception { User user = User.builder() .userName("王毅凌") .passWord("100101") .userSex(UserSexEnum.MAN) .nickName("asdasdf") .build(); log.info("封裝的user對象爲:" + user); userRepository.insert(user); List<User> all = userRepository.getAll(); all.forEach(System.out::println); } @Test public void update() throws Exception { User user = User.builder() .id(38l) .userName("王毅凌") .passWord("010101") .userSex(UserSexEnum.MAN) .nickName("毅凌") .build(); userRepository.update(user); List<User> all = userRepository.getAll(); all.forEach(System.out::println); } @Test public void delete() throws Exception { log.info("刪除前:"); userRepository.getAll().forEach(System.out::println); userRepository.delete(41l); log.info("刪除後:"); userRepository.getAll().forEach(System.out::println); } @Test public void getList() throws Exception { UserParam userParam = UserParam.builder() .build(); userParam.setCurrentPage(2); userParam.setPageSize(3); log.info("起始頁碼爲:" + userParam.getBeginLine()); List<User> list = userRepository.getList(userParam); list.forEach(System.out::println); } @Test public void getCount() throws Exception { UserParam userParam = UserParam.builder() .build(); userParam.setCurrentPage(2); userParam.setPageSize(3); Integer count = userRepository.getCount(userParam); log.info("記錄數爲:" + count); } @Test public void testDemo() { } }
View Code

   4.10 多數據源

    4.10.1 多數據源配置文件

spring: datasource: primary: jdbc-url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
 username: root password: 182838 driver-class-name: com.mysql.cj.jdbc.Driver secondary: jdbc-url: jdbc:mysql://localhost:3306/testdemo4?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
 username: root password: 182838 driver-class-name: com.mysql.cj.jdbc.Driver

    4.10.2 數據源名稱

      技巧01:利用一個接口來管理數據源名稱,也能夠利用一個枚舉類型來實現

package com.xunyji.mybatis_xml_datasource.config.datasource; /** * @author 王楊帥 * @create 2018-12-27 15:22 * @desc 多數據源名稱 **/
public interface DataSourceNames { String FIRST = "first"; String SECOND = "second"; }
DataSourceNames.java

    4.10.3 擴展Spring的AbstractRoutingDataSource抽象類

      AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是實現多數據 源的核心,並對該方法進行Override。

package com.xunyji.mybatis_xml_datasource.config.datasource; import com.sun.xml.internal.bind.v2.util.DataSourceSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @author 王楊帥 * @create 2018-12-27 15:23 * @desc 讀取配置文件中的數據源信息 **/ @Configuration public class DynamicDataSourceConfig { @Bean @ConfigurationProperties(value = "spring.datasource.primary") public DataSource firstDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(value = "spring.datasource.secondary") public DataSource secondDataSource() { return DataSourceBuilder.create().build(); } @Bean @Primary public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) { Map<String, DataSource> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceNames.FIRST, firstDataSource); targetDataSources.put(DataSourceNames.SECOND, secondDataSource); return new DynamicDataSource(firstDataSource, targetDataSources); } }
DynamicDataSourceConfig.java

    4.10.4 配置DataSource,指定數據源的信息

package com.xunyji.mybatis_xml_datasource.config.datasource; import com.sun.xml.internal.bind.v2.util.DataSourceSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @author 王楊帥 * @create 2018-12-27 15:23 * @desc 讀取配置文件中的數據源信息 **/ @Configuration public class DynamicDataSourceConfig { /** * 讀取第一個數據源信息 * @return */ @Bean @ConfigurationProperties(value = "spring.datasource.primary") public DataSource firstDataSource() { return DataSourceBuilder.create().build(); } /** * 讀取第二個數據源信息 * @return */ @Bean @ConfigurationProperties(value = "spring.datasource.secondary") public DataSource secondDataSource() { return DataSourceBuilder.create().build(); } /** * 配置主數據源,並將數據源信息集合傳入DynamicDataSource中 * @param firstDataSource * @param secondDataSource * @return */ @Bean @Primary public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) { Map<String, DataSource> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceNames.FIRST, firstDataSource); targetDataSources.put(DataSourceNames.SECOND, secondDataSource); return new DynamicDataSource(firstDataSource, targetDataSources); } }
DynamicDataSourceConfig.java

    4.10.5 數據源切換註解

package com.xunyji.mybatis_xml_datasource.config.datasource; import java.lang.annotation.*; /** * 數據源切換註解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { String name() default ""; }
DataSource.java

    4.10.6 用於切換數據源的切面

package com.xunyji.mybatis_xml_datasource.config.datasource; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * @author 王楊帥 * @create 2018-12-27 15:58 * @desc 切面類 **/ @Aspect @Component @Slf4j public class DataSourceAspect implements Ordered { @Pointcut("@annotation(com.xunyji.mybatis_xml_datasource.config.datasource.DataSource)") public void dataSourcePointCut() {} @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); DataSource ds = method.getAnnotation(DataSource.class); if(ds == null){ DynamicDataSource.setDataSource(DataSourceNames.FIRST); log.debug("set datasource is " + DataSourceNames.FIRST); }else { DynamicDataSource.setDataSource(ds.name()); log.debug("set datasource is " + ds.name()); } try { return point.proceed(); } finally { DynamicDataSource.clearDataSource(); log.debug("clean datasource"); } } @Override public int getOrder() { return 1; } }
DataSourceAspect.java

    4.10.7 啓動類配置

      因爲SpringBoot的自動配置原理,因此須要將SpringBoot配置的DataSource排除掉

    4.10.8 添加AOP依賴

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

    4.10.9 利用註解切換數據源

      在Repository層中的方法中利用自定義的數據源切換註解標註該方法使用的數據源

 

相關文章
相關標籤/搜索