目标:使得读写分离和业务代码低耦合
实现思想:在网上也看了很多相关实现,都不是很完整,下面简单说说我自己的实现,期间参考了其他人的一些思想,具体如下:
使用的是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>
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;
}
}
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());
}
}