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層中的方法中利用自定義的數據源切換註解標註該方法使用的數據源