欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

springboot + mybatis配置多数据源示例

程序员文章站 2024-03-06 10:02:25
在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源。 代码结构: 简要原理: 1)databasetype列出所有的数据源的key---...

在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源。

代码结构:

springboot + mybatis配置多数据源示例

简要原理:

1)databasetype列出所有的数据源的key---key

2)databasecontextholder是一个线程安全的databasetype容器,并提供了向其中设置和获取databasetype的方法

3)dynamicdatasource继承abstractroutingdatasource并重写其中的方法determinecurrentlookupkey(),在该方法中使用databasecontextholder获取当前线程的databasetype

4)mybatisconfig中生成2个数据源datasource的bean---value

5)mybatisconfig中将1)和4)组成的key-value对写入到dynamicdatasource动态数据源的targetdatasources属性(当然,同时也会设置2个数据源其中的一个为dynamicdatasource的defaulttargetdatasource属性中)

6)将dynamicdatasource作为primary数据源注入到sqlsessionfactory的datasource属性中去,并且该datasource作为transactionmanager的入参来构造datasourcetransactionmanager

7)使用的时候,在dao层或service层先使用databasecontextholder设置将要使用的数据源key,然后再调用mapper层进行相应的操作,建议放在dao层去做(当然也可以使用spring aop+自定注解去做)

注意:在mapper层进行操作的时候,会先调用determinecurrentlookupkey()方法获取一个数据源(获取数据源:先根据设置去targetdatasources中去找,若没有,则选择defaulttargetdatasource),之后在进行数据库操作。

 1、假设有两个数据库,配置如下

application.properties

#the first datasource
jdbc.driverclassname = com.mysql.jdbc.driver
jdbc.url = jdbc:mysql://xxx:3306/mytestdb?zerodatetimebehavior=converttonull&useunicode=true&characterencoding=utf-8
jdbc.username = root
jdbc.password = 123

#the second datasource
jdbc2.driverclassname = com.mysql.jdbc.driver
jdbc2.url = jdbc:mysql://xxx:3306/mytestdb2?zerodatetimebehavior=converttonull&useunicode=true&characterencoding=utf-8
jdbc2.username = root
jdbc2.password = 123

说明:在之前的配置的基础上,只增加了上述的第二个数据源。 

2、databasetype

package com.xxx.firstboot.common.datasource;

/**
 * 列出所有的数据源key(常用数据库名称来命名)
 * 注意:
 * 1)这里数据源与数据库是一对一的
 * 2)databasetype中的变量名称就是数据库的名称
 */
public enum databasetype {
  mytestdb,mytestdb2
}

作用:列举数据源的key。

3、databasecontextholder

package com.xxx.firstboot.common.datasource;

/**
 * 作用:
 * 1、保存一个线程安全的databasetype容器
 */
public class databasecontextholder {
  private static final threadlocal<databasetype> contextholder = new threadlocal<>();
  
  public static void setdatabasetype(databasetype type){
    contextholder.set(type);
  }
  
  public static databasetype getdatabasetype(){
    return contextholder.get();
  }
}

作用:构建一个databasetype容器,并提供了向其中设置和获取databasetype的方法

4、dynamicdatasource

package com.xxx.firstboot.common.datasource;

import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource;

/**
 * 动态数据源(需要继承abstractroutingdatasource)
 */
public class dynamicdatasource extends abstractroutingdatasource {
  protected object determinecurrentlookupkey() {
    return databasecontextholder.getdatabasetype();
  }
}

作用:使用databasecontextholder获取当前线程的databasetype 

5、mybatisconfig

package com.xxx.firstboot.common;

import java.util.hashmap;
import java.util.map;
import java.util.properties;

import javax.sql.datasource;

import org.apache.ibatis.session.sqlsessionfactory;
import org.mybatis.spring.sqlsessionfactorybean;
import org.mybatis.spring.annotation.mapperscan;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.beans.factory.annotation.qualifier;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.context.annotation.primary;
import org.springframework.core.env.environment;
import org.springframework.core.io.support.pathmatchingresourcepatternresolver;
import org.springframework.jdbc.datasource.datasourcetransactionmanager;

import com.alibaba.druid.pool.druiddatasourcefactory;
import com.xxx.firstboot.common.datasource.databasetype;
import com.xxx.firstboot.common.datasource.dynamicdatasource;

/**
 * springboot集成mybatis的基本入口 1)创建数据源(如果采用的是默认的tomcat-jdbc数据源,则不需要)
 * 2)创建sqlsessionfactory 3)配置事务管理器,除非需要使用事务,否则不用配置
 */
@configuration // 该注解类似于spring配置文件
@mapperscan(basepackages = "com.xxx.firstboot.mapper")
public class mybatisconfig {

  @autowired
  private environment env;

  /**
   * 创建数据源(数据源的名称:方法名可以取为xxxdatasource(),xxx为数据库名称,该名称也就是数据源的名称)
   */
  @bean
  public datasource mytestdbdatasource() throws exception {
    properties props = new properties();
    props.put("driverclassname", env.getproperty("jdbc.driverclassname"));
    props.put("url", env.getproperty("jdbc.url"));
    props.put("username", env.getproperty("jdbc.username"));
    props.put("password", env.getproperty("jdbc.password"));
    return druiddatasourcefactory.createdatasource(props);
  }

  @bean
  public datasource mytestdb2datasource() throws exception {
    properties props = new properties();
    props.put("driverclassname", env.getproperty("jdbc2.driverclassname"));
    props.put("url", env.getproperty("jdbc2.url"));
    props.put("username", env.getproperty("jdbc2.username"));
    props.put("password", env.getproperty("jdbc2.password"));
    return druiddatasourcefactory.createdatasource(props);
  }

  /**
   * @primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
   * @qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个datasource类型的实例)
   */
  @bean
  @primary
  public dynamicdatasource datasource(@qualifier("mytestdbdatasource") datasource mytestdbdatasource,
      @qualifier("mytestdb2datasource") datasource mytestdb2datasource) {
    map<object, object> targetdatasources = new hashmap<>();
    targetdatasources.put(databasetype.mytestdb, mytestdbdatasource);
    targetdatasources.put(databasetype.mytestdb2, mytestdb2datasource);

    dynamicdatasource datasource = new dynamicdatasource();
    datasource.settargetdatasources(targetdatasources);// 该方法是abstractroutingdatasource的方法
    datasource.setdefaulttargetdatasource(mytestdbdatasource);// 默认的datasource设置为mytestdbdatasource

    return datasource;
  }

  /**
   * 根据数据源创建sqlsessionfactory
   */
  @bean
  public sqlsessionfactory sqlsessionfactory(dynamicdatasource ds) throws exception {
    sqlsessionfactorybean fb = new sqlsessionfactorybean();
    fb.setdatasource(ds);// 指定数据源(这个必须有,否则报错)
    // 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
    fb.settypealiasespackage(env.getproperty("mybatis.typealiasespackage"));// 指定基包
    fb.setmapperlocations(
        new pathmatchingresourcepatternresolver().getresources(env.getproperty("mybatis.mapperlocations")));//

    return fb.getobject();
  }

  /**
   * 配置事务管理器
   */
  @bean
  public datasourcetransactionmanager transactionmanager(dynamicdatasource datasource) throws exception {
    return new datasourcetransactionmanager(datasource);
  }

}

作用:

  • 通过读取application.properties文件生成两个数据源(mytestdbdatasource、mytestdb2datasource)
  • 使用以上生成的两个数据源构造动态数据源datasource
    • @primary:指定在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错(一般用于多数据源的情况下)
    • @qualifier:指定名称的注入,当一个接口有多个实现类的时候使用(在本例中,有两个datasource类型的实例,需要指定名称注入)
    • @bean:生成的bean实例的名称是方法名(例如上边的@qualifier注解中使用的名称是前边两个数据源的方法名,而这两个数据源也是使用@bean注解进行注入的)
  • 通过动态数据源构造sqlsessionfactory和事务管理器(如果不需要事务,后者可以去掉)

 6、使用

shopmapper:

package com.xxx.firstboot.mapper;

import org.apache.ibatis.annotations.param;
import org.apache.ibatis.annotations.result;
import org.apache.ibatis.annotations.results;
import org.apache.ibatis.annotations.select;

import com.xxx.firstboot.domain.shop;

public interface shopmapper {

  @select("select * from t_shop where id = #{id}")
  @results(value = { @result(id = true, column = "id", property = "id"),
            @result(column = "shop_name", property = "shopname") })
  public shop getshop(@param("id") int id);

}

shopdao:

package com.xxx.firstboot.dao;

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.repository;

import com.xxx.firstboot.common.datasource.databasecontextholder;
import com.xxx.firstboot.common.datasource.databasetype;
import com.xxx.firstboot.domain.shop;
import com.xxx.firstboot.mapper.shopmapper;

@repository
public class shopdao {
  @autowired
  private shopmapper mapper;

  /**
   * 获取shop
   */
  public shop getshop(int id) {
    databasecontextholder.setdatabasetype(databasetype.mytestdb2);
    return mapper.getshop(id);
  }
}

注意:首先设置了数据源的key,然后调用mapper(在mapper中会首先根据该key从动态数据源中查询出相应的数据源,之后取出连接进行数据库操作)

shopservice:

package com.xxx.firstboot.service;

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;

import com.xxx.firstboot.dao.shopdao;
import com.xxx.firstboot.domain.shop;

@service
public class shopservice {

  @autowired
  private shopdao dao;

  public shop getshop(int id) {
    return dao.getshop(id);
  }
}

shopcontroller:

package com.xxx.firstboot.web;

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.requestmethod;
import org.springframework.web.bind.annotation.requestparam;
import org.springframework.web.bind.annotation.restcontroller;

import com.xxx.firstboot.domain.shop;
import com.xxx.firstboot.service.shopservice;

import io.swagger.annotations.api;
import io.swagger.annotations.apioperation;

@restcontroller
@requestmapping("/shop")
@api("shopcontroller相关api")
public class shopcontroller {

  @autowired
  private shopservice service;

  @apioperation("获取shop信息,测试多数据源")
  @requestmapping(value = "/getshop", method = requestmethod.get)
  public shop getshop(@requestparam("id") int id) {
    return service.getshop(id);
  }

}

补:其实databasecontextholder和dynamicdatasource完全可以合为一个类

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。