====== Spring+Mybatis 多数据源配置_2 ======
===== 方法描述 =====
采用spring配置文件配置动态数据源,利于强大的AOP切面,根据service实现类的自定义注解或者指定参数判断,在spring事务管理获取动态数据源之前,设置我们希望连接的数据源,从而灵活配置
===== 优缺点 =====
- 一套统一配置,通过注解或者入参来灵活判断设置数据源,代码级别判断,配置不需要动,方便可控
- 不仅适合两个数据库没有相关性的情况,而且还适合master-slave性的多数据源的配置
- vo,dao,mapper,service仅需要一套
===== 碰到的坑 =====
- spring-mvc对controller的扫描路径一定要写到web不能把sevice包含进去,或者指定扫描@Controller注解,否则会把service扫进去,那spring事务管理扫面将不起作用
- 事务管理注解驱动[transaction]一定要配置在AOP切面[transactionPointcut](事务管理获取动态数据源)之前,否则注解会无效
- xml中如果已经配置事务transactionAdvice,service方法上可以不配置注解
- 如果在事务中抛出异常Exception,则会回滚,不需要配置rollback-for="Exception"
- AOP切面[dataSourceAspect](动态设置数据源)一定要配置在AOP切面[transactionPointcut](事务管理获取动态数据源)之前,否则先获取完再设置数据源会无效
===== 暂未解决 =====
使用自定义注解打在service的方法上,不知道为啥,切面里获取不到注解。但是,换了一种方法,使用入参送入枚举,切面里获取入参判断枚举值来设置数据源,可以搞定!
===== 主要代码 =====
==== spring-mvc.xml ====
UTF-8
ignore
0.##
text/plain;charset=utf-8
html/text;charset=utf-8
text/html;charset=utf-8
text/json;charset=utf-8
application/json;charset=utf-8
atom=application/atom+xml
html=text/html
json=application/json
xml=application/xml
*=*/*
==== application-context.xml ====
==== Datasource.java ====
package com.gxx.record.core;
/**
*
* - Title:
* -
* none
*
* - Description:
* -
*
none
*
*
*
* @author Gxx
* @version 1.0, 2016年1月12日
* @since record
*
*/
public enum Datasource {
//主库
MASTER,
//备库
SLAVE
}
==== DatabaseContextHolder.java ====
package com.gxx.record.core;
/**
*
* - Title:
* -
* none
*
* - Description:使用ThreadLocal设置和获取当前线程的数据源KEY
* -
*
none
*
*
*
* @author Gxx
* @version 1.0, 2016年1月12日
* @since record
*
*/
public class DatabaseContextHolder {
/**
* ThreadLocal
*/
private static final ThreadLocal contextHolder = new ThreadLocal();
/**
* 设置数据源KEY
* @param customerType
*/
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
/**
* 获取数据源KEY
* @return
*/
public static String getCustomerType() {
return contextHolder.get();
}
/**
* 清空数据源KEY
*/
public static void clearCustomerType() {
contextHolder.remove();
}
}
==== DynamicDataSource.java ====
package com.gxx.record.core;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
*
* - Title:
* -
* none
*
* - Description:动态获取数据源KEY,继承父类AbstractRoutingDataSource
* -
*
none
*
*
*
* @author Gxx
* @version 1.0, 2016年1月12日
* @since record
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource{
/**
* 获取数据源值KEY
*/
@Override
protected Object determineCurrentLookupKey() {
return DatabaseContextHolder.getCustomerType();
}
}
==== DataSourceInterceptor.java ====
package com.gxx.record.core;
import org.aspectj.lang.JoinPoint;
import org.springframework.stereotype.Component;
/**
*
* - Title:
* -
* none
*
* - Description:数据源切面拦截器
* -
*
none
*
*
*
* @author Gxx
* @version 1.0, 2016年1月12日
* @since record
*
*/
@Component
public class DataSourceInterceptor {
/**
* 设置数据源KEy
* @param jp
*/
public void setDataSource(JoinPoint jp) {
/**
* 清空数据源KEY
*/
DatabaseContextHolder.clearCustomerType();
/**
* 这里不知道为啥,使用注册方式注解方法或者入参,这里取不到注解,才改成入参送指定枚举的方式
*/
for(Object param : jp.getArgs()){
if(param instanceof Datasource){
Datasource datasource = (Datasource)param;
if(Datasource.MASTER.name().equals(datasource.name())){
DatabaseContextHolder.setCustomerType(Datasource.MASTER.name());
} else if(Datasource.SLAVE.name().equals(datasource.name())){
DatabaseContextHolder.setCustomerType(Datasource.SLAVE.name());
}
}
}
}
}
==== UserService.java ====
package com.gxx.record.service;
import com.gxx.record.base.vo.User;
import com.gxx.record.core.Datasource;
/**
*
* - Title:
* -
* 用户服务接口
*
* - Description:
* -
*
none
*
*
*
* @author Administrator
* @version 1.0, 2015年6月18日
* @since record
*
*/
public interface UserService {
/**
* 新增用户
* @param datasource 使用枚举参数,动态设置数据源
* @param user
*/
public void doSaveUser(Datasource datasource, User user);
/**
* 根据姓名查用户
* @param datasource 使用枚举参数,动态设置数据源
* @param name
* @return
*/
public User getUserByName(Datasource datasource, String name);
}
==== UserServiceImpl.java ====
package com.gxx.record.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.gxx.record.base.dao.UserMapper;
import com.gxx.record.base.vo.User;
import com.gxx.record.core.Datasource;
import com.gxx.record.service.UserService;
/**
*
* - Title:
* -
* 用户服务实现类
*
* - Description:
* -
*
none
*
*
*
* @author Administrator
* @version 1.0, 2015年6月18日
* @since record
*
*/
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userDao;
/**
* 新增用户
* @param datasource 使用枚举参数,动态设置数据源
* @param user
*/
//xml中已经配置transactionAdvice,这里可以不配置注解
//@Transactional(readOnly=false, propagation=Propagation.REQUIRED)
public void doSaveUser(Datasource datasource, User user){
userDao.insert(user);
//如果在事务中抛出异常,则会回滚
//throw new RuntimeException("异常发生");
}
/**
* 根据姓名查用户
* @param datasource 使用枚举参数,动态设置数据源
* @param name
* @return
*/
public User getUserByName(Datasource datasource, String name){
return userDao.getUserByName(name);
}
}
==== UserController.java ====
package com.gxx.record.web.user;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.gxx.record.base.vo.User;
import com.gxx.record.core.Datasource;
import com.gxx.record.dto.UserDto;
import com.gxx.record.service.UserService;
/**
*
* - Title:
* -
* none
*
* - Description:用户控制器
* -
*
none
*
*
*
* @author Administrator
* @version 1.0, 2016年1月12日
* @since record
*
*/
@Controller
@RequestMapping("/user/")
public class UserController {
/**
* 日志处理器
*/
private final Logger logger = Logger.getLogger(UserController.class);
@Autowired
private UserService userService;
@RequestMapping(value = "/preRegistFtl", method = RequestMethod.GET)
public String preRegistFtl() {
return "user/preRegistFtl";
}
@RequestMapping(value = "/preRegistJsp", method = RequestMethod.GET)
public String preRegistJsp() {
return "user/preRegistJsp";
}
/**
* 注册
* @param request
* @param userDto
* @return
*/
@RequestMapping(value = "/registJsp",produces="application/json")
public @ResponseBody UserDto registJsp(HttpServletRequest request, UserDto userDto) {
logger.info("用户注册:主从=[" + userDto.getMasterOrSlave() + "],姓名=[" + userDto.getName() + "],密码=[" + userDto.getPassword() + "]");
/**
* 1.判用户名是否存在
*/
User user = null;
if(StringUtils.equals(userDto.getMasterOrSlave(), UserDto.MASTER)){
user = userService.getUserByName(Datasource.MASTER, userDto.getName());
} else if(StringUtils.equals(userDto.getMasterOrSlave(), UserDto.SLAVE)){
user = userService.getUserByName(Datasource.SLAVE, userDto.getName());
} else {
userDto.setSuccess(Boolean.FALSE.booleanValue());
userDto.setMessage("主还是从值[" + userDto.getMasterOrSlave() + "]有误!");
return userDto;
}
if(user != null){
userDto.setSuccess(Boolean.FALSE.booleanValue());
userDto.setMessage("用户名[" + userDto.getName() + "]已存在!");
return userDto;
}
/**
* 2.创建用户对象 并 新增用户
*/
user = new User();
user.setName(userDto.getName());
user.setPassword(userDto.getPassword());
user.setCreateDate("20150618");
user.setCreateTime("000000");
if(StringUtils.equals(userDto.getMasterOrSlave(), UserDto.MASTER)){
userService.doSaveUser(Datasource.MASTER, user);
} else if(StringUtils.equals(userDto.getMasterOrSlave(), UserDto.SLAVE)){
userService.doSaveUser(Datasource.SLAVE, user);
}
/**
* 3.返回结果
*/
userDto.setSuccess(Boolean.TRUE.booleanValue());
userDto.setMessage("注册成功!");
return userDto;
}
/**
* 注册
* @param request
* @param userDto
* @return
*/
@RequestMapping(value = "/registFtl")
public String registFtl(HttpServletRequest request, UserDto userDto) {
logger.info("用户注册:主从=[" + userDto.getMasterOrSlave() + "],姓名=[" + userDto.getName() + "],密码=[" + userDto.getPassword() + "]");
/**
* 1.判用户名是否存在
*/
User user = null;
if(StringUtils.equals(userDto.getMasterOrSlave(), UserDto.MASTER)){
user = userService.getUserByName(Datasource.MASTER, userDto.getName());
} else if(StringUtils.equals(userDto.getMasterOrSlave(), UserDto.SLAVE)){
user = userService.getUserByName(Datasource.SLAVE, userDto.getName());
} else {
userDto.setSuccess(Boolean.FALSE.booleanValue());
userDto.setMessage("主还是从值[" + userDto.getMasterOrSlave() + "]有误!");
return "user/result";
}
if(user != null){
userDto.setSuccess(Boolean.FALSE.booleanValue());
userDto.setMessage("用户名[" + userDto.getName() + "]已存在,请更改用户名!");
return "user/result";
}
/**
* 2.创建用户对象 并 新增用户
*/
user = new User();
user.setName(userDto.getName());
user.setPassword(userDto.getPassword());
user.setCreateDate("20150618");
user.setCreateTime("000000");
if(StringUtils.equals(userDto.getMasterOrSlave(), UserDto.MASTER)){
userService.doSaveUser(Datasource.MASTER, user);
} else if(StringUtils.equals(userDto.getMasterOrSlave(), UserDto.SLAVE)){
userService.doSaveUser(Datasource.SLAVE, user);
}
/**
* 3.返回结果
*/
userDto.setSuccess(Boolean.TRUE.booleanValue());
userDto.setMessage("注册成功!");
return "user/result";
}
}
===== 附上代码 =====
{{:分享:技术:数据源:record_dynamicdatasource.zip|}}