spring+hibernate多租户切换数据库、数据源方案(也可用来分表)

使用场景
一套程序,多个租户使用,开发的时候只针对单个租户开发,省去每条sql的一个查询条件;好处是每个租户使用自己的库,数据库自然就分布了,当然这样给数据库的管理带来一些不便。
网上有很多资料有讲用NamingStrategy重写classToTableName方法来实现,但是应用中用到sql怎么办?


具体实现
主要使用到了hibernate的过滤器,重写EmptyInterceptor中的public String onPrepareStatement(String sql) 方法


首先定义一个DbInfo类


//本类用于传递数据库相关信息,字段不够可自己扩展
public class DbInfo { 


private String ip;
private String dbName;
private String user;
private String pass;
private int port;
private String type;
private static String[] tables;


public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}

public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getDbName() {
return dbName;
}
public void setDbName(String dbName) {
this.dbName = dbName;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public static String[] getTables() {
return tables;
}
public static void setTables(String[] tables) {
DbInfo.tables = tables;
}
}




在应用的入口根据租户id(能区分租户的都可以)获取相关租户的数据库信息,封装在DbInfo里面,然后放入ThreadLocal中,定义一个ThreadLocal工具类
public class ThreadLocalUtil {


private static final ThreadLocal<Map<String, Object>> contextHolder = new ThreadLocal<Map<String, Object>>();
 
public static void setObject(String key, Object obj) {
Map<String, Object> map= contextHolder.get();
if(map==null){
map=new HashMap<String, Object>();
contextHolder.set(map);
}
map.put(key, obj);
}


public static Object getObject(String key) {
return contextHolder.get()==null?null:contextHolder.get().get(key); 
}


public static void remove() {
contextHolder.remove();
}
}


TableInfo.setTables(new String[]{"pre_table01","pre_table02","pre_table03"});//定义应用有哪些表,其实后面可以从sql中提取表名的(用正则需要500毫秒左右,效率很低),但是为了提 高效率,这里把有哪些表提前定义。


//开始封装具体数据库信息,这些信息应该是动态的,和租户相关的
DbInfo di=new DbInfo();
TableInfo ti=new TableInfo();
di.setIp("127.0.0.1");
di.setUser("root");
di.setPass("123456");
di.setPort(3306);
di.setDbName("db01");
di.setType("mysql");
ThreadLocalUtil.setObject("dbInfo", di);


//重写 onPrepareStatement方法,每条hibernate要执行的sql都会经过这里
public class MyHibernateInterceptor extends EmptyInterceptor {  
/**

*/
private static final long serialVersionUID = 1L;


@Override
public String onPrepareStatement(String sql) {
System.out.println("sql:"+sql);
return super.onPrepareStatement(getNewSql(sql));


private static String getNewSql(String sql){
DbInfo di=(DbInfo) ThreadLocalUtil.getObject("dbInfo");
if(di!=null){ 
for(String table:DbInfo.getTables()){
sql=sql.replace(table, di.getDbName()+"."+table);//有没有更高效的写法,请指点

return sql;
}

return sql;
}
}
不过要在sessionFactory的配置中加上这个属性
<property name="entityInterceptor">
<bean id ="auditInterceptor" class="com.xxx.MyHibernateInterceptor" />
</property>


至此,可以动态切换数据库了,但是这要求在一台服务器上的数据库,下面说说切换数据源
自己写一个MultiDataSource实现DataSource接口,重点是写一个获取数据源的方法或者工厂类
private DataSource getDataSource();//这里根据不同数据库ip返回不同的dataSource,dataSource可以用Map缓存起来,ip作为key; 具体用那种连接池,都在里面实现。这些可以写一个工 厂类来实现,我这代码还不够完善,完善了补上


<!-- 配置dataSource ,如果不需要切换数据源,修改 class即可--> 
<bean id="dataSource" class="com.xxx.MultiSource" destroy-method="close">
...
</bean>

你可能感兴趣的:(分库,多租户,hibernate分表,切换数据源)