1、读写分离:可以通过Spring提供的AbstractRoutingDataSource类,重写determineCurrentLookupKey方法,实现动态切换数据源的功能;读写分离可以有效减轻写库的压力,又可以把查询数据的请求分发到不同读库;MySql主从同步传送门
2、写数据库:当调用insert、update、delete及一些实时数据用到的库;
3、读数据库:当调用select查询数据用到的库;
4、JaveWeb工程通过AbstractRoutingDataSource类实现读写分离;
5、jdbc.properties配置如下:
datasource.type=mysql
datasource.autoCommitOnClose=false
datasource.driverClassName=com.mysql.jdbc.Driver
#writer
w.datasource.url=jdbc:mysql://localhost:3306/zyfund?useUnicode=true&characterEncoding=UTF-8
w.datasource.username=root
w.datasource.password=123456
#reader
r.datasource.url=jdbc:mysql://192.168.37.128:3306/zyfund?useUnicode=true&characterEncoding=UTF-8
r.datasource.username=root
r.datasource.password=123456
6.application.xml文件:
<!-- 引入配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--创建读的数据源 -->
<bean id="dataSourceRead" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${datasource.driverClassName}"/>
<property name="url" value="${r.datasource.url}"/>
<property name="username" value="${r.datasource.username}"/>
<property name="password" value="${r.datasource.password}"/>
<property name="maxActive" value="${datasource.maxActive}"/>
<property name="maxIdle" value="${datasource.maxIdle}"/>
<property name="minIdle" value="${datasource.minIdle}"/>
</bean>
<!-- 创建写的数据库 -->
<bean id="dataSourceWriter" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${datasource.driverClassName}"/>
<property name="url" value="${w.datasource.url}"/>
<property name="username" value="${w.datasource.username}"/>
<property name="password" value="${w.datasource.password}"/>
<property name="maxActive" value="${datasource.maxActive}"/>
<property name="maxIdle" value="${datasource.maxIdle}"/>
<property name="minIdle" value="${datasource.minIdle}"/>
</bean>
<!-- 动态数据源 -->
<bean id="dynamicDataSource" class="com.zyfund.admin.utils.aop.DynamicDataSource">
<!-- 通过key-value关联数据源 -->
<property name="targetDataSources">
<map>
<entry value-ref="dataSourceWriter" key="dataSourceWriter"></entry>
<entry value-ref="dataSourceRead" key="dataSourceRead"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourceWriter" />
</bean>
<!-- <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxActive}"/>
<property name="maxIdle" value="${redis.pool.maxIdle}"/>
<property name="maxWaitMillis" value="${redis.pool.maxWait}"/>
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/>
<property name="testOnReturn" value="${redis.pool.testOnReturn}" />
</bean>
<bean id="jedisShardInfo" class="redis.clients.jedis.JedisShardInfo">
<constructor-arg index="0" value="${redis.ip}"/>
<constructor-arg index="1" value="${redis.port}" type="int"/>
</bean>
<bean id="jedisPool" class="redis.clients.jedis.ShardedJedisPool">
<constructor-arg index="0" ref="jedisPoolConfig" />
<constructor-arg index="1">
<list>
<ref bean="jedisShardInfo" />
</list>
</constructor-arg>
</bean> -->
<!-- 配置SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="com.zyfund.admin.utils.BaseSqlSessionFactoryBean">
<!-- 通过动态数据源获取sqlSessionFactory对象 -->
<property name="dataSource" ref="dynamicDataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
<property name="mapperLocations" value="classpath*:com/**/*Mapper.xml"/>
</bean>
<!-- 定义日志处理类 -->
<bean id="logAdvice" class="com.zyfund.admin.utils.aop.LogAdvice" />
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dynamicDataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="do*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="merge*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="enable*" propagation="REQUIRED" />
<tx:method name="sync*" propagation="REQUIRED" />
<!-- <tx:method name="*" read-only="true"/> -->
<!-- <tx:method name="add*" rollback-for="Throwable"/> --><!-- 在add开头的方法中遇到异常(Throwable)就回滚-->
</tx:attributes>
</tx:advice>
<aop:config >
<aop:pointcut expression="execution (* com.zyfund.admin.service..*.*(..))" id="services" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="services" />
</aop:config>
7、编写动态数据源处理类(dynamicDataSource
package com.zyfund.admin.utils.aop;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
Object obj = DBHelper.getDbType();
return obj;
}
}
):
8、编写DBHelper帮助类(数据源切换类):
package com.zyfund.admin.utils.aop;
import org.apache.commons.lang.StringUtils;
/****
* 动态切换数据源的帮助类
* @author Administrator
*
*/
public class DBHelper {
private static ThreadLocal<String> dbContext = new ThreadLocal<String>();
// 写数据源标识
public final static String DB_WRITE = "dataSourceWriter";
// 读数据源标识
public final static String DB_READ = "dataSourceRead";
/**
* 获取数据源类型,即是写数据源,还是读数据源
*
* @return
*/
public static String getDbType() {
String db_type = dbContext.get();
if (StringUtils.isEmpty(db_type)) {
// 默认是写数据源
db_type = DB_WRITE;
}
return db_type;
}
/**
* 设置该线程的数据源类型
*
* @param str
*/
public static void setDbType(String str) {
dbContext.set(str);
}
}
9.service实现类切换数据源:
/*@Aspect 此注解会影响数据源切换,运行代码得知不加的话会先执行DynamicDataSource里的determineCurrentLookupKey方法,后执行Service层里DBHelper.setDbType()方法,导致数据源切换失败!
*/
@Aspect
@Service
public class UserManageServiceImpl implements UserManageService{
private static Logger log = Logger.getLogger(UserManageServiceImpl.class);
@Autowired
private UserManageDao userManageDao;
public UserManage login(String loginName, String password) {
DBHelper.setDbType(DBHelper.DB_READ);
return userManageDao.login(loginName, password);
}
public boolean exists(String username) {
DBHelper.setDbType(DBHelper.DB_READ);
UserManage user = userManageDao.exists(username);
return user!=null?true:false;
}
public boolean add(UserManage user) {
DBHelper.setDbType(DBHelper.DB_WRITE);
user.setCreateat(new Date());
return userManageDao.add(user);
}
public boolean updatePwd(String username,String password) {
DBHelper.setDbType(DBHelper.DB_WRITE);
return userManageDao.updatePwd(username,password);
}
public UserManage getById(String id) {
DBHelper.setDbType(DBHelper.DB_READ);
return userManageDao.getById(Integer.parseInt(id));
}
public boolean update(UserManage user) {
DBHelper.setDbType(DBHelper.DB_WRITE);
return userManageDao.update(user);
}
public boolean delete(String id) {
DBHelper.setDbType(DBHelper.DB_WRITE);
return userManageDao.delete(Integer.parseInt(id));
}
public List<UserManageVo> getAllList(Integer page, String search,String phone) {
DBHelper.setDbType(DBHelper.DB_READ);
Integer pageSize = 20;
log.info("start:"+page*pageSize+";page:"+page);
if(search==null){
search = "";
}
List<UserManageVo> list =new ArrayList<UserManageVo>();
List<UserManage> userManages = userManageDao.getSearchList(page*pageSize, pageSize,search,phone);
for(UserManage userManage :userManages){
UserManageVo vo =new UserManageVo();
BeanUtils.copyProperties(userManage, vo);
list.add(vo);
}
return list;
}
public Integer getSearchListCount(String search,String phone) {
// TODO 自动生成的方法存根
DBHelper.setDbType(DBHelper.DB_READ);
return userManageDao.getSearchListCount(search,phone);
}
}