天天看點

90天Java(3)---mybatis與mybatis plus-2

前言

此博文是學習呂一明老師《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的多資料源處理

  1. 通過掃描包區分

    ​​​參考源碼:​​ 項目結構:

  2. 90天Java(3)---mybatis與mybatis plus-2
  3. 關鍵代碼如下:
@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);
    }

}
      
  1. 通過動态資料源區分

MyBatis 的讀寫分離

原理跟多資料源類似,MyBatis做讀寫分離的方式有多種,通過攔截發起請求的方法或執行的sql來自動判斷需要的資料源!

  1. 攔截發起操作的方法名

    需要自己約定增删改查的字首,然後根據字首選擇資料源!

    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();
  }
      
  1. 截發起操作的sql

    git demo:​​​https://github.com/shawntime/shawn-rwdb​​

MyBatis plus的簡單運用