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层中的方法中利用自定义的数据源切换注解标注该方法使用的数据源

 

相关文章
相关标签/搜索