spring aop 实现读写分离

目标:使得读写分离和业务代码低耦合

实现思想:在网上也看了很多相关实现,都不是很完整,下面简单说说我自己的实现,期间参考了其他人的一些思想,具体如下:

使用的是maven搭建工程,pom.xml内容如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.spring.mvc.test</groupId>
	<artifactId>springmb</artifactId>
	<packaging>war</packaging>
	<version>0.1</version>
	<name>springmb Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<properties>
		<spring.version>3.2.4.RELEASE</spring.version>
		<mybatis.version>3.2.4</mybatis.version>
		<slf4j.version>1.6.6</slf4j.version>
		<log4j.version>1.2.9</log4j.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.8</version>
			<scope>test</scope>
		</dependency>
		<!-- spring核心包 -->
		<!-- springframe start -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-oxm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<!-- springframe end -->
		<!-- mybatis核心包 -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>${mybatis.version}</version>
		</dependency>
		<!-- mybatis/spring包 -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>1.2.2</version>
		</dependency>
		<!-- mysql驱动包 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.29</version>
		</dependency>

		<!-- 阿里巴巴数据源包 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.2</version>
		</dependency>
		<dependency>  
		    <groupId>c3p0</groupId>  
		    <artifactId>c3p0</artifactId>  
		    <version>0.9.1.2</version>  
      </dependency>
		<!-- json数据 -->
		<dependency>
			<groupId>org.codehaus.jackson</groupId>
			<artifactId>jackson-mapper-asl</artifactId>
			<version>1.9.13</version>
		</dependency>
		<!-- 日志文件管理包 -->
		<!-- log start -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>${log4j.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${slf4j.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>${slf4j.version}</version>
		</dependency>
		<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-orm</artifactId>
				<version>2.5.5</version>
			</dependency>
		<!-- log end -->
		<dependency>
				<groupId>cglib</groupId>
				<artifactId>cglib-nodep</artifactId>
				<version>2.1_3</version>
			</dependency>
			<!-- ibatis framework-->
			<dependency>
				<groupId>org.apache.ibatis</groupId>
				<artifactId>ibatis-sqlmap</artifactId>
				<version>2.3.4.726</version>
			</dependency>
			<dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.5.4</version>
      </dependency>
      <dependency>
        <groupId>aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.5.0</version>
      </dependency>
	</dependencies>
	
	<build>
		<finalName>springmb</finalName>
	</build>
</project>

spring配置如下:
创建目录:src/main/resources 下创建包conf,conf中添加如下文件:
 jdbc.properties、mybatis-config.xml、spring-mybatis.xml、spring.xml
jdbc.properties文件中添加mysql配置:
jdbc_driverClassName=com.mysql.jdbc.Driver
jdbc_url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8
jdbc_username=test
jdbc_password=123456
mybatis-config.xml文件中添加mybatis相关配置加载文件:
<?xml version="1.0" encoding="GB2312"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
    <sqlMap resource="mapper/mytest-sqlmap-mapping.xml"/>
</sqlMapConfig>
spring-mybatis.xml文件中添加spring相关配置,其中会添加spring aop切面中切入点触发的相关操作,即解析自定义注解:
其中数据库连接片段可以使用jdbc.properties中的配置使用spring加载properties的方式灵活配置,这里我直接写死了测试数据库,大家可以自己重新配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-3.2.xsd
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
  http://www.springframework.org/schema/util
  http://www.springframework.org/schema/util/spring-util-3.2.xsd">
	<bean id="masterdataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass">
			<value>com.mysql.jdbc.Driver</value>
		</property>
		<property name="user">
			<value>test</value>
		</property>
		<property name="password">
			<value>123456</value>
		</property>
		<property name="jdbcUrl">
			<value>jdbc:mysql://127.0.0.1:3306/master?useUnicode=true&characterEncoding=utf-8</value>
		</property>
	</bean>
	<bean id="slavedataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass">
			<value>com.mysql.jdbc.Driver</value>
		</property>
		<property name="user">
			<value>test</value>
		</property>
		<property name="password">
			<value>123456</value>
		</property>
		<property name="jdbcUrl">
			<value>jdbc:mysql://127.0.0.1:3306:3306/slave?useUnicode=true&characterEncoding=utf-8</value>
		</property>
	</bean>

	<bean id="dataSource" class="cn.springmvc.util.DynamicDataSource">
        <property name="targetDataSources">  
              <map key-type="java.lang.String">  
                  <!-- write -->
                 <entry key="master" value-ref="masterdataSource"/>  
                 <!-- read -->
                 <entry key="slave" value-ref="slavedataSource"/>  
              </map>  
              
        </property>  
        <property name="defaultTargetDataSource" ref="masterdataSource"/>  
    </bean>
<!-- 此处应注入ibatis配置文件,而非sqlMap文件,否则会出现“there is no statement.....异常”
	<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
		
		<property name="configLocation">
			<value>classpath:conf/mybatis-config.xml</value>
		</property>

	</bean> -->
	<bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
     <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation">
        <value>classpath:conf/mybatis-config.xml</value>
        </property>
    </bean>
    <bean id="testDAO" class="cn.springmvc.dao.TestDaoServiceImpl">
		<property name="dataSource">
			<ref bean="dataSource" />
		</property>
		<property name="sqlMapClient">
			<ref bean="sqlMapClient" />
		</property>
	</bean>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <bean id="manyDataSourceAspect" class="cn.springmvc.util.DataSourceAspect" />
    <aop:config>
        <aop:aspect id="c" ref="manyDataSourceAspect">
        	<aop:before method="before" pointcut="execution(* cn.springmvc.dao.*.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

spring.xml中为spring配置,配置扫描器,以及开始annotation:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd"
    default-autowire="byName">
  <!-- 引入jdbc配置文件
  <context:property-placeholder location="classpath:conf/jdbc.properties"/> -->
  <!-- 扫描文件(自动将servicec层注入) -->
  <context:annotation-config/>
  <context:component-scan base-package="cn.springmvc">
  <context:include-filter type="annotation"
            expression="org.springframework.stereotype.Component" />
  </context:component-scan>
</beans>
src/main/resources 下创建包mapper,mapper中添加如下文件:
mytest-sqlmap-mapping.xml,配置具体执行的sql语句,如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" >
<sqlMap namespace="system">
	<resultMap id="testLog" class="cn.springmvc.model.TestLogBO">
		<result column="id" property="id" javaType="Long" jdbcType="int" />
		<result column="user_id" property="userId" javaType="Long" jdbcType="BigInt" />
		<result column="card_num" property="CardNum" javaType="java.lang.String" jdbcType="varchar" />
		<result column="binding_activity" property="bindingActivity" javaType="int" jdbcType="int" />
		<result column="activity_name" property="activityName" javaType="java.lang.String" jdbcType="varchar" />
		<result column="create_time" property="createTime" javaType="Long" jdbcType="int" />
		<result column="user_ip" property="userIp" javaType="java.lang.String" jdbcType="varchar" />
		<result column="reserved1" property="reserved1" javaType="java.lang.String" jdbcType="varchar" />
		<result column="reserved2" property="reserved2" javaType="java.lang.String" jdbcType="varchar" />
	</resultMap>
    
    <select id="findById" resultMap="testLog">
    	select * from test_log where (id = #id#)
    </select>
    
</sqlMap>

下面为具体的java代码,首先需要说明的是,我们需要通过注解的形式进行读写分离,那么就需要进行自定义注解,代码如下:
package cn.springmvc.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
	String value();
}
aop切入点执行代码如下:
package cn.springmvc.util;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;


public class DataSourceAspect {

	public void before(JoinPoint point){
		Object target = point.getTarget();
		String method = point.getSignature().getName();
		Class<?>[] classz = target.getClass().getInterfaces();
		Class<?>[] parameterTypes = ((MethodSignature)point.getSignature()).getParameterTypes();
		try{
			Method m = classz[0].getMethod(method, parameterTypes);
			if(m != null && m.isAnnotationPresent(DataSource.class)){
				DataSource date = m.getAnnotation(DataSource.class);
				DynamicDataSourceHolders.putDataSource(date.value());
				System.out.println(date.value()+"----------------");
			}
		}catch(Exception e){
			
		}
	}
}
获取数据源重写需要重写,这里重写了spring的AbstractRoutingDataSource的determineCurrentLookupKey方法如下:
package cn.springmvc.util;

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

public class DynamicDataSource extends AbstractRoutingDataSource{

	@Override
	protected Object determineCurrentLookupKey() {
		
		return DynamicDataSourceHolders.getDataSource();
	}
	
}
该类通过操作ThreadLocal为线程中数据源赋值和获取值的静态方法,具体如下:
package cn.springmvc.util;

public class DynamicDataSourceHolders{
	public static final ThreadLocal<String> holder = new ThreadLocal<String>();
	
	public static void putDataSource(String name){
		holder.set(name);
	}
	
	public static String getDataSource(){
		return holder.get();
	}
}
下面就是如何使用的java代码,如下:
需要创建一个类,如我这里使用的是TestLog.java
package cn.springmvc.model;

public class TestLogBO implements java.io.Serializable{

private static final long serialVersionUID = 7746993991494850224L;
	
	public Long id ;
	public Long userId ;
	public String cardNum ;
	public Integer bindingActivity ;
	public Long createTime ;
	public String activityName;
	public String userIp;
	public String reserved1 ;//预留字段1
	public String reserved2 ;//预留字段2
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public Long getUserId() {
		return userId;
	}
	public void setUserId(Long userId) {
		this.userId = userId;
	}
	public String getCardNum() {
		return cardNum;
	}
	public void setCardNum(String cardNum) {
		this.cardNum = cardNum;
	}
	public Integer getBindingActivity() {
		return bindingActivity;
	}
	public void setBindingActivity(Integer bindingActivity) {
		this.bindingActivity = bindingActivity;
	}
	public String getActivityName() {
		return activityName;
	}
	public void setActivityName(String activityName) {
		this.activityName = activityName;
	}
	public String getUserIp() {
		return userIp;
	}
	public void setUserIp(String userIp) {
		this.userIp = userIp;
	}
	public Long getCreateTime() {
		return createTime;
	}
	public void setCreateTime(Long createTime) {
		this.createTime = createTime;
	}
	public String getReserved1() {
		return reserved1;
	}
	public void setReserved1(String reserved1) {
		this.reserved1 = reserved1;
	}
	public String getReserved2() {
		return reserved2;
	}
	public void setReserved2(String reserved2) {
		this.reserved2 = reserved2;
	}
}
创建了一个DAO接口,如TestDaoService.java,代码如下:
package cn.springmvc.dao;

import cn.springmvc.model.UserBankCardChangeLogDO;
import cn.springmvc.util.DataSource;

public interface TestDaoService {

	@DataSource("master")
	public TestLogBO getUserById(Long Id);
}
大家可以再接口代码中可以看到了我们的自定义注解(@dataSource("master")),如果注解的参数为slave的话(@dataSource("slave"))的话,则该接口中的数据源为从库数据源,这样我们在实现类中的业务逻辑中就无需进行是通过主库还是从库来进行操作的java代码了。

具体实现如下:
package cn.springmvc.dao;

import java.util.HashMap;
import java.util.Map;

import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

import cn.springmvc.model.UserBankCardChangeLogDO;

public class TestDaoServiceImpl extends SqlMapClientDaoSupport implements TestDaoService {

	@Override
	public TestLogBO getUserById(Long id) {
		// TODO Auto-generated method stub
		Map<String,Long> map  = new HashMap<String,Long>();
		map.put("id", id);
		TestLogBO testLog= (TestLogBO)getSqlMapClientTemplate().queryForObject("findById", map);
		return testLog;
	}

}

下面为我们的测试代码,这里我使用的是junit进行了单元测试,代码如下:
package cn.springmvc.test;

import javax.annotation.Resource;

import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;

import cn.springmvc.dao.IDAO;
import cn.springmvc.model.UserBankCardChangeLogDO;

@ContextConfiguration(locations = {"classpath:conf/spring.xml",
		"classpath:conf/spring-mybatis.xml"})
public class Test extends AbstractJUnit4SpringContextTests{

	@Resource
	private TestDaoService testDAO;
	
	@org.junit.Test
	public void test(){
		TestLogBO dd = testDAO.getUserById(29L);
		System.out.println(dd.getUserId());
	}
	
}

结束,这只是一个简单的demo,如果大家在项目中使用的话,需要将对主从库的监控也加入,并且一边不时一主已从的模式,大多是一住多从的模式,这样,我们就需要对从库进行负载,具体的负载网上很多,自己可以通过单间的模运算进行负载,或者其他的负载方式进行,自己对文字组织能力比较差,希望大家多多谅解。







  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值