前言
此博文是學習呂一明老師《90天Java進階訓練營二期》的筆記總結,接上一篇博文。我們接着來介紹和分析MyBatis
本次分析中涉及到的代碼和資料庫表均放在GitHub上,位址:源碼位址
目錄
本篇博文将按照如下順序來介紹MyBatis的相關知識。
- MyBatis 如何擷取資料庫中的表、字段
- MyBatis 的多資料源處理
- MyBatis 的讀寫分離
- MyBatis plus的簡單運用
MyBatis如何擷取資料庫中的表、字段
原理
information_schema資料庫是MySQL自帶的,它提供了通路資料庫中繼資料的方式。什麼是中繼資料呢?中繼資料是關于資料的資料,如資料庫名或表名,列的資料類型,或通路權限等。有些時候用于表述該資訊的其他術語包括“資料詞典”和“系統目錄”。
在MySQL中,把 information_schema 看作是一個資料庫,确切說是資訊資料庫。其中儲存着關于MySQL伺服器所維護的所有其他資料庫的資訊。如資料庫名,資料庫的表,表欄的資料類型與通路權 限等。在INFORMATION_SCHEMA中,有數個隻讀表。它們實際上是視圖,而不是基本表,是以,你将無法看到與之相關的任何檔案。
information_schema表說明
SCHEMATA表:提供了目前mysql執行個體中所有資料庫的資訊。是show databases的結果取之此表。
TABLES表:提供了關于資料庫中的表的資訊(包括視圖)。詳細表述了某個表屬于哪個schema,表類型,表引擎,建立時間等資訊。是show tables from schemaname的結果取之此表。
COLUMNS表:提供了表中的列資訊。詳細表述了某張表的所有列以及每個列的資訊。是show columns from schemaname.tablename的結果取之此表。
STATISTICS表:提供了關于表索引的資訊。是show index from schemaname.tablename的結果取之此表。
USER_PRIVILEGES(使用者權限)表:給出了關于全程權限的資訊。該資訊源自mysql.user授權表。是非标準表。
SCHEMA_PRIVILEGES(方案權限)表:給出了關于方案(資料庫)權限的資訊。該資訊來自mysql.db授權表。是非标準表。
TABLE_PRIVILEGES(表權限)表:給出了關于表權限的資訊。該資訊源自mysql.tables_priv授權表。是非标準表。
COLUMN_PRIVILEGES(列權限)表:給出了關于列權限的資訊。該資訊源自mysql.columns_priv授權表。是非标準表。
CHARACTER_SETS(字元集)表:提供了mysql執行個體可用字元集的資訊。是SHOW CHARACTER SET結果集取之此表。
COLLATIONS表:提供了關于各字元集的對照資訊。
COLLATION_CHARACTER_SET_APPLICABILITY表:指明了可用于校對的字元集。這些列等效于SHOW COLLATION的前兩個顯示字段。
TABLE_CONSTRAINTS表:描述了存在限制的表。以及表的限制類型。
KEY_COLUMN_USAGE表:描述了具有限制的鍵列。
ROUTINES表:提供了關于存儲子程式(存儲程式和函數)的資訊。此時,ROUTINES表不包含自定義函數(UDF)。名為“mysql.proc name”的列指明了對應于INFORMATION_SCHEMA.ROUTINES表的mysql.proc表列。
VIEWS表:給出了關于資料庫中的視圖的資訊。需要有show views權限,否則無法檢視視圖資訊。
TRIGGERS表:提供了關于觸發程式的資訊。必須有super權限才能檢視該表
查找目前資料庫的表資訊:
第一種方法:
select * from information_schema.TABLES where TABLE_SCHEMA=(select database())
第二種方法:
#擷取表資訊
show table status
查找目前表的所有字段資訊:
第一種方法:
select * from information_schema.COLUMNS where TABLE_SCHEMA = (select database()) and TABLE_NAME=#{tableName}
第二種方式
show full fields from `student`;
MyBatis 的多資料源處理
單獨使用MyBatis ,隻需要在xml檔案中配置多個
environment
,生産SqlSessionFactory的時候指定environment即可實作多資料源。
<!-- 設定一個預設的連接配接環境資訊 -->
<environments default="development">
<!--連接配接環境資訊,取一個任意唯一的名字 -->
<environment id="development">
<!-- mybatis使用jdbc事務管理方式 -->
<transactionManager type="JDBC"/>
<!-- mybatis使用連接配接池方式來擷取連接配接 -->
<dataSource type="POOLED">
<!-- 配置與資料庫互動的4個必要屬性 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<environment id="development2">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url2}"/>
<property name="username" value="${jdbc.username2}"/>
<property name="password" value="${jdbc.password2}"/>
</dataSource>
</environment>
</environments>
內建Spring的多資料源處理
-
通過掃描包區分
參考源碼: 項目結構:
- 關鍵代碼如下:
@Configuration
//掃描包
@MapperScan(basePackages = "com.neo.mapper.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSource1Config {
@Bean(name = "test1DataSource")
// 注入資料源
@ConfigurationProperties(prefix = "spring.datasource.test1")
@Primary
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
// 建立SqlSessionFactory
@Bean(name = "test1SqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml"));
return bean.getObject();
}
// 事務的初始化
@Bean(name = "test1TransactionManager")
@Primary
public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test1SqlSessionTemplate")
@Primary
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- 通過動态資料源區分
MyBatis 的讀寫分離
原理跟多資料源類似,MyBatis做讀寫分離的方式有多種,通過攔截發起請求的方法或執行的sql來自動判斷需要的資料源!
-
攔截發起操作的方法名
需要自己約定增删改查的字首,然後根據字首選擇資料源!
git demo:https://github.com/XWxiaowei/90-Java-demo-2/tree/master/course-3-mybatis/bounterMybatis spring-mybatis.xml 中的配置配置了主庫和從庫(主寫從讀),在配置檔案中配置預設的資料源是
。masterDataSource
<!-- Master資料源 -->
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
....
</bean>
<!-- Slave資料源 -->
<bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
...
</bean>
<!-- 自定義動态資料源 -->
<bean id="dataSource" class="com.bounter.mybatis.extension.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- 配置讀寫資料源 -->
<entry value-ref="masterDataSource" key="write"></entry>
<entry value-ref="slaveDataSource" key="read"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource"></property>
</bean>
代碼解釋:
首先定義了一個方法名的攔截器
DataSourceAspect
,在方法執行之前擷取方法名,然後跟定義的方法清單(讀庫的)比較,如果方法名的字首再方法清單中,則将資料源的key切換成讀庫的key。該key放在了
DynamicDataSourceHolder
類中的内部對象ThreadLocal中。保證了線程安全。
@Component
@Aspect
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {
//讀庫資料源key
private static final String DATASOURCE_KEY_READ = "read";
//查詢方法清單
String[] queryMethods = {"find","get","query","count","select"};
/**
* dao層方法執行前選擇資料源
* @param point
*/
@Before("execution(* com.bounter.mybatis.dao..*.*(..))")
public void before(JoinPoint point) {
// 擷取到目前執行的方法名
String methodName = point.getSignature().getName();
//比對查詢方法
for(String queryMethod : queryMethods) {
if(methodName.startsWith(queryMethod)) {
//查詢方法設定資料源為讀庫
DynamicDataSourceHolder.setDataSource(DATASOURCE_KEY_READ);
System.out.println("methodName-->" + methodName + " 目前資料庫-->" + DATASOURCE_KEY_READ.toString());
break;
}
}
}
/**
* dao層方法執行完後清空資料源選擇
* @param point
*/
@After("execution(* com.bounter.mybatis.dao..*.*(..))")
public void after(JoinPoint point) {
DynamicDataSourceHolder.clearDataSource();
}
}
設定資料源的key
private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<>();
public static void setDataSource(String dataSourceKey) {
dataSourceHolder.set(dataSourceKey);
}
然後,就是調用到
AbstractRoutingDataSource
類的
getConnection
方法擷取Connection對象。
public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
}
在
getConnection()
方法内部首先調用
determineTargetDataSource()
擷取
DataSource
(資料源)。
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
//使用DynamicDataSourceHolder保證線程安全
return DynamicDataSourceHolder.getDataSource();
}
-
截發起操作的sql
git demo:https://github.com/shawntime/shawn-rwdb