在这里,我们接上一篇文章,利用JNDI访问应用服务器配置的两个数据源来模拟同时操作不同的数据库如同时操作mysql和oracle等。实际上,上个例子可能用来模拟mysql数据库主从配置读写分离更贴切些。既然如此,在本例中,我们就完成读写分离的模拟在web端的配置实例。
续上次的例子,关于JNDI数据源的配置和spring datasource的配置这里不再重复。下面着重加入AOP实现DAO层动态分库调用。可先看上篇文章《spring学习笔记(18)使用JNDI模拟访问应用服务器多数据源实例 》
1. DAO层设计
/***************接口设计************/
public interface MyBaseDao {
SessionFactory getSessionFactory();
<E> E add(Object object);
<E>E queryUnique(Class<E> clazz, Integer entityId);
void setSourceType(Integer sourceType);
}
/****************实现类设计******************/
@Repository
public class MyBaseDaoImpl implements MyBaseDao{
//由于本类是单例了,考虑到线程安全问题,这是使用ThreadLocal作为判断调用哪个数据源的依据
private ThreadLocal<Integer> sourceType = new ThreadLocal<Integer>();
@Autowired
@Qualifier("sessionFactory")
private SessionFactory sessionFactory;
@Autowired
@Qualifier("sessionFactory2")
private SessionFactory sessionFactory2;
@Override
public SessionFactory getSessionFactory() {
if(sourceType.get() == null){//如果没有值则默认使用数据源1
sourceType.set(1);
}
switch (sourceType.get()) {
case 1://使用数据源1,本例中这里是主库,主要负责写
return sessionFactory;
case 2: //使用数据源2,本例中这里是从库,主要负责读
return sessionFactory2;
default:
throw new IllegalArgumentException("unknown sourceType");
}
}
@Override//模拟一个写的操作,要让主库数据源调用
public <E> E add(Object object) {
return (E) getSessionFactory().openSession().save(object);
}
@Override//模拟一个读的操作,要让从库数据源调用
public <E> E queryUnique(Class<E> clazz, Integer entityId) {
return (E) getSessionFactory().openSession().get(clazz, entityId);
}
@Override//共AOP增强类修改数据源类型
public void setSourceType(Integer sourceType) {
this.sourceType.set(sourceType);
}
}
2. AOP类设计
@Aspect
public class DataSourceSelector {//使用前置增强
@Before("execution( * com.yc.dao.MyBaseDaoImpl.add*(..))")//写操作
public void before1(JoinPoint joinPoint){
((MyBaseDao)joinPoint.getTarget()).setSourceType(1);//切换到主库
}
@Before("execution( * com.yc.dao.MyBaseDaoImpl.query*(..))")//读操作
public void before2(JoinPoint joinPoint){
((MyBaseDao)joinPoint.getTarget()).setSourceType(2);//切换到从库
}
}
关于AOP的配置教程,可移步参考本系列前面AOP部分的文章。然后我们还需要在IOC容器中注册我们的切面。
<aop:aspectj-autoproxy /> <!-- 使@AspectJ注解生效 -->
<bean class="com.yc.aop.DataSourceSelector" /><!-- 注册切面 -->
3. 修改控制器
针对上一篇文章的控制器,作如下修改:
@Controller
public class JNDITestController {
@Autowired
private MyBaseDao myBaseDao;
@RequestMapping("testJNDI")
@ResponseBody
public Object testJNDI(){
User user = new User();
user.setName("new_user_fron_yc1");
Integer newId = myBaseDao.add(user);
System.out.println("new UserId = " + newId);//获取我们新插入的id
System.out.println("is new User here? " + myBaseDao.queryUnique(com.yc.model2.User.class, newId));
return newId;
}
}
4. 测试与结果分析
运行服务器,然后在游览器中输入http://localhost:8090/yc/testJNDI
,我们会看到控制台打印信息:
new UserId = 4
is new User here? null
newUserId是我们插入到主库中的,id为4。
但我们紧接着读取,却并未读取到。这说明我们存的数据库和读的数据库并不是同一个,从而简单地实现了读写分离。为了验证这一点,我们所示数据库,如下图所示,显然我们在yc1存进去了我们的测试数据,在yc2中并没有!