JDBC(Java Data Base Connectivity,Java 数据库链接)是一种用于执行 SQL 语句的 Java API,能够为多种关系数据库提供统一访问,它由一组用 Java 语言编写的类和接口组成。JDBC 提供了一种基准,据此能够构建更高级的工具和接口,使数据库开发人员可以编写数据库应用程序。html
JDBC 就是一套 Java 访问数据库的 API 规范,利用这套规范屏蔽了各类数据库 API 调用的差别性;java
当 Java 程序须要访问数据库时,直接调用 JDBC API 相关代码进行操做,JDBC 调用各种数据库的驱动包进行交互,最后数据库驱动包和对应的数据库通信,完成 Java 程序操做数据库。mysql
直接在 Java 程序中使用 JDBC 比较复杂,须要 7 步才能完成数据库的操做:git
加载数据库驱动 -> 创建数据库链接 -> 建立数据库操做对象 -> 编写SQL语句 -> 利用数据库操做对象执行数据库操做 -> 获取并操做结果集 -> 关闭链接对象和操做对象,回收资源web
利用JDBC操做数据库参考博文spring
因为JDBC操做数据库很是复杂,因此牛人们编写了不少ORM框架,其中Hibernate、Mybatis、SpringJDBC最流行;sql
时间又过了N年,数据库
又有牛人在Hibernate的基础上开发了SpringDataJPA,在Mybatis的基础上开发出了MybatisPlus。apache
引入 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>
<?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依赖图能够知道 spring-boot-starter-jdbc 依赖了spring-jdbc、HikariCP;
spring-jdbc主要提供JDBC操做相关的接口,HikariCP就是传说中最快的链接池。
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;
坑: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
版案例使用了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; }
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); }
技巧:在实现类中依赖注入 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); } } }
技巧:新增、更新、删除都是利用JdbcTemplate的update方法,update方法的返回值是执行成功的记录数(需在实现类中根据结果判断是否操做成功);
技巧:利用JdbcTemplate的queryForObject方法查询单条记录,利用JdbcTemplate的query查询多条记录;
技巧:利用JdbcTemplate的queryForObject方法查询单条记录时若是查询不到就会抛出EmptyResultDataAccessException,须要进行捕获。
技巧:查询记录时须要对结果集进行封装,能够直接利用BeanPropertyRowMapper实例进行封装,或者自定义一个实现了UserRowMapper的实现类
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); } }
待更新...
待更新...
思路:配置多个数据源 -> 经过配置读取两个数据源 -> 利用读取到数据源分别建立各自的JdbcTemplate对应的Bean并交给Spring容器管理 -> 在调用持久层中的方法时动态传入JdbcTemplate实例
技巧: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
技巧:ConfigurationProperties用于方法上时会自动读取配置文件中的值并设置到该方法的返回对象上
技巧:@Primary 注解的做用是当依赖注入有多个类型相同的Bean时,添加了@Primary的那个Bean会默认被注入
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); } }
这种硬编码方式实现多数据源时,须要在调用持久层方法时指定动态的JdbcTemplate
思路:配置多个数据源 -> 读取数据源信息 -> 利用AbstractRoutingDataSource抽象类配置数据源信息 -> 利用AOP实现动态数据源切换
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
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(); } }
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); } }
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 ""; }
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"; }
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; } }
错区1:因为利用AOP实现的所数据源,因此须要额外导入Aspect相关的依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency>
错误2:启动应用后会出现循环应用错误,错误信息以下
缘由:SpringBoot默认对数据源进行了配置,若是想要动态数据源生效就必须关闭数据源的自动配置
解决:在启动类的注解中排除掉数据源自动配置类便可
》AbstractRoutingDataSource原理解析
(摘自:https://gitbook.cn/gitchat/column/5b86228ce15aa17d68b5b55a/topic/5be8f0552c33167c317c6a7f)
对象关系映射(Object Relational Mapping,ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM 是经过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
当你开发一个应用程序的时候(不使用 O/R Mapping),可能会写很多数据访问层代码,用来从数据库保存、删除、读取对象信息等;在 DAL 中写了不少的方法来读取对象数据、改变状态对象等任务,而这些代码写起来老是重复的。针对这些问题 ORM 提供了解决方案,简化了将程序中的对象持久化到关系数据库中的操做。
ORM 框架的本质是简化编程中操做数据库的编码,在 Java 领域发展到如今基本上就剩两家最为流行,一个是宣称能够不用写一句 SQL 的 Hibernate,一个是以动态 SQL 见长的 MyBatis,二者各有特色。在企业级系统开发中能够根据需求灵活使用,会发现一个有趣的现象:传统企业大都喜欢使用 Hibernate,而互联网行业一般使用 MyBatis。
(摘自:https://gitbook.cn/gitchat/column/5b86228ce15aa17d68b5b55a/topic/5be8f0552c33167c317c6a7f)
MyBatis 支持普通的 SQL 查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎全部的 JDBC 代码和参数的手工设置以及对结果集的检索封装。MaBatis 可使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。
SQL 被统一提取出来,便于统一管理和优化
SQL 和代码解耦,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰、更易维护、更易单元测试
提供映射标签,支持对象与数据库的 ORM 字段关系映射
提供对象关系映射标签,支持对象关系组件维护
灵活书写动态 SQL,支持各类条件来动态生成不一样的 SQL
编写 SQL 语句时工做量很大,尤为是字段多、关联表多时,更是如此
SQL 语句依赖于数据库,致使数据库移植性差
待更新2018年12月27日10:54:46
引入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>
mapper接口:就是通常的持久层接口而已,通常放在repository包下;mapper接口位于程序源文件目录
mapper文件:就是书写sql语句的文件,每一个mapper映射文件都和一个mapper接口一一对应;mapper映射文件和mybatis的配置文件都位于resources目录
mybatis配置文件:就是mybatis的一些基本配置信息;mybatis配置文件通常和存放mappr映射文件的目录处于一个目录下【本案例处于resouces目录下的mybatis目录下】
路径配置:因为mybatis官方提供的启动包提供了一个配置类来配置mapper映射文件和配置文件的路径【org.mybatis.spring.boot.autoconfigure.MybatisProperties】
项目结构以下所示:
配置类:org.mybatis.spring.boot.autoconfigure.MybatisProperties,可配置的选项和配置实例以下所示
在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配置文件位于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>
在数据源对应的数据库中建立一张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);
在项目中建立一个实体类类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 }
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; }
技巧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); }
技巧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>
须要在启动类上指定mapper接口路径
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
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() { } }
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
技巧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"; }
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); } }
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); } }
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 ""; }
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; } }
因为SpringBoot的自动配置原理,因此须要将SpringBoot配置的DataSource排除掉
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
在Repository层中的方法中利用自定义的数据源切换注解标注该方法使用的数据源