Giter VIP home page Giter VIP logo

magic-dao's Introduction

magic-dao

1. 解决问题

  • 简化数据库访问(通过对JdbcTemplate的封装使得单表增删改查无需写任何SQL)

  • 多数据源读写分离(默认写master库读slave库,支持指定从master库或者任一slave库读取)

  • 分表(同时支持单一字段和多字段分表;支持用户自定义分表处理逻辑)

使用过程中如有任何疑问请加QQ(1609924522)沟通

2. 设计说明

...

3. 使用说明

magic-dao 已经上传到Maven**仓库中(http://search.maven.org/),项目中添加如下依赖即可导入。

<dependency>
	<groupId>com.github.rongzhu8888</groupId>
	<artifactId>magic-dao</artifactId>
	<version>1.0.1</version>
</dependency>

内部依赖spring-jdbc:4.2.3.RELEASE、 spring-aspects:4.2.3.RELEASE,如果spring版本冲突,可以将内部依赖排除:

<dependency>
	<groupId>com.github.rongzhu8888</groupId>
	<artifactId>magic-dao</artifactId>
	<version>1.0.1</version>
	<exclusions>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </exclusion>
    </exclusions>
</dependency>

3.1 数据源配置

  • 单数据源

      <!-- 可以被替换为JNDI、DBCP等任何数据源 -->
      <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      	<property name="user" value="${mysql.username}" />
      	<property name="password" value="${mysql.password}" />
      	<property name="driverClass" value="${mysql.driver_class}" />
      	<property name="jdbcUrl" value="${mysql.url}" />
      	<property name="maxPoolSize" value="${mysql.maxPoolSize}" />
      	<property name="minPoolSize" value="${mysql.minPoolSize}" />
      	<property name="initialPoolSize" value="${mysql.initialPoolSize}" />
      	<property name="maxIdleTime" value="${mysql.maxIdleTime}" />
      	<property name="checkoutTimeout" value="${mysql.checkoutTimeout}" />
      	<property name="acquireIncrement" value="${mysql.acquireIncrement}" />
      	<property name="acquireRetryAttempts" value="${mysql.acquireRetryAttempts}" />
      	<property name="acquireRetryDelay" value="${mysql.acquireRetryDelay}" />
      	<property name="autoCommitOnClose" value="${mysql.autoCommitOnClose}" />
      	<property name="automaticTestTable" value="${mysql.automaticTestTable}" />
      	<property name="breakAfterAcquireFailure" value="${mysql.breakAfterAcquireFailure}" />
      	<property name="idleConnectionTestPeriod" value="${mysql.idleConnectionTestPeriod}" />
      	<property name="maxStatements" value="${mysql.maxStatements}" />
      	<property name="maxStatementsPerConnection" value="${mysql.maxStatementsPerConnection}" />
      </bean>
    
      <bean id="singleDataSource" class="MagicSingleDataSource">
      	<property name="dataSource" ref="myDataSource" />
      </bean>
    
  • 多数据源(读写分离)

    默认写master库,读slave库(如果开发者需要定制哪些service或者业务需要读master库,请见3.5 读写分离模块)

      <bean id="master" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      	...
      </bean>
    
      <bean id="slave1" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      	...
      </bean>
    
      <bean id="slave2" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      	...
      </bean>
    
      <bean id="multiDataSource" class="MagicMultipleDataSource">
      	<property name="master" ref="master" />
      	<property name="slaves">
      		<map>
      			<entry key="slave1" value-ref="slave1" /> <!-- key表示该数据源的alias -->
      			<entry key="slave2" value-ref="slave2" />
      	</map>
      	</property>
      </bean>
    

3.2 PO与Column映射配置

magic-dao 提供了三类注解用以配置实体对象与表字段的映射关系。

@Table :PO类注解,表示当前实体对应表名

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Table {

	String name();

}

@Key :PO类属性注解,表示当前属性对应的字段为主键

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Key {

    String column();

    boolean autoIncrement() default false;

}

@Column :PO类属性注解,表示当前属性对应表中的字段名

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {

    String value();

    boolean readOnly() default false; //not need to insert and update

}

我们将从以下三种情况说明如何配置PO对象与Table的映射关系:

  • 普通唯一主键

      @Table(name = "mc_app")
      public class AppPo implements Serializable {
    
      	@Key(column = "app_id")
          private String appId;
    
          @Column(value = "app_name")
          private String appName;
    
          @Column(value = "app_code")
          private String appCode;
    
          @Column(value = "group_id")
          private String groupId;
    
          @Column(value = "create_time")
          private Date createTime;
    
          @Column(value = "update_time", readOnly = true)
          private Date updateTime;
    
          ... <省略getXxx和setXxx方法>
      }
    
  • 自增唯一主键

      @Table(name = "mc_app")
      public class AppPo implements Serializable {
    
      	@Key(column = "id", autoIncrement = true)
          private Long id;
    
          @Column(value = "app_name")
          private String appName;
    
          @Column(value = "app_code")
          private String appCode;
    
          @Column(value = "group_id")
          private String groupId;
    
          @Column(value = "create_time")
          private Date createTime;
    
          @Column(value = "update_time", readOnly = true)
          private Date updateTime;
    
          ... <省略getXxx和setXxx方法>
      }
    
  • 联合主键

      //主键对象
      public class UserRoleKey implements Serializable {
    
          @Key(column = "user_id")
          private Long userId;
    
          @Key(column = "role_id")
      	private String roleId;
    
      	... <省略getXxx和setXxx方法>
    
      }
    
      ...
    
      //PO对象
      @Table(name = "mc_user_role")
      public class UserRolePo extends UserRoleKey {
    
      	@Column(value = "create_time")
          private Date createTime;
    
      	... <省略getXxx和setXxx方法>
    
      }
    

###3.3 Dao接口与实现类 ###

magic-dao 对单表访问(增删改查)非常方便,只需简单定义该表的接口与实现类(接口继承MagicDao , 实现类继承MagicGenericDao )即可。

MagicDao接口的泛型为<KEY, ENTITY>,具体用法如下:

  • 唯一主键

      //接口
      public interface MagicAppDao extends MagicDao<Long, AppPo> {
    
      }
    
      ...
    
      //实现类
      public class MagicAppDaoImpl extends MagicGenericDao<Long, AppPo> implements MagicAppDao {
    
      }
    
  • 联合主键

      //接口
      public interface UserRoleDao extends MagicDao<UserRoleKey, UserRolePo> {
    
      }
    
      ...
    
      //实现类
      public class UserRoleDaoImpl extends MagicGenericDao<UserRoleKey, UserRolePo> implements MagicAppDao {
    
      }
    

###3.4 事务 ### 直接使用Spring的DataSourceTransactionManager即可,注意多数据源场景下DataSourceTransactionManager的dataSource属性应该配置为指向master库的数据源。

<!-- 事务管理器 -->
<bean id="transactionManager"
	  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="master" />
</bean>
<!-- 事务注解驱动,标注@Transactional的类和方法将具有事务性 -->
<tx:annotation-driven transaction-manager="transactionManager" />

###3.5 读写分离 ### 我们知道,多数据源读写分离场景中,一般是写master库,读slave库。magic-dao 默认情况下也是如此,开发者只需提供多个数据源,并配置到MagicMultipleDataSource 实例中即可,无需做任何额外配置。

然而,某些场景对数据的实时性要求非常高,需要从master库读取数据,更极端的情况下,某些数据需要从指定的slave库中读取,对于这些需求,magic-dao同样支持。

magic-dao 提供了**@QueryDataSource** 注解和RuntimeQueryDataSourceAop 来满足该需求。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface QueryDataSource {

    String alias() default ""; //指定完成数据读取的从库别名

    DataSourceType type() default DataSourceType.SLAVE; //指定从主库OR从库读取数据,默认从库

}

使用方法:

在Service实现类或者其具体某个方法上添加 @QueryDataSource 注解,并且在spring容器(applicationContext.xml)中添加RuntimeQueryDataSourceAop 配置。

  • QueryDataSource注解

    (1)类级别注解

      //对该类中所有方法均有效
      @QueryDataSource(alias = "slave1")
      public class AppServiceImpl {
    
      	...
    
      }
    

    (2)方法级别注解

      public class UserServiceImpl {
    
      	//仅对该方法有效
      	@QueryDataSource(type = DataSourceType.MASTER)
      	public void getAttentionList() {
    
      		...
    
      	}
    
      ...
    
      }
    
  • AOP配置

      <bean id="dataSourceAop" class="pers.zr.opensource.magic.dao.runtime.RuntimeQueryDataSourceAop"></bean>
      <aop:config>
      	<aop:aspect ref="dataSourceAop">
      		<aop:around
      			method="determine"
      			pointcut="execution(* demo.xxx..*ServiceImpl*.*(..))" /> <!-- 这里视情况而定,无需拦截所有的ServiceImpl -->
      	</aop:aspect>
      </aop:config>
    

注意:

(1) @QueryDataSource注解在类上,表示该类所有的方法都应用此注解;

(2) 方法级别的注解较类级别优先级高,如果方法和类同时具有@QueryDataSource注解,则取方法级别注解;

(3) @QueryDataSource的alias属性比type优先级高,如果指定了alias,则忽略type属性。

(4) @QueryDataSource同时支持在接口或者实现类中注解

###3.6 分表 ###

magic-dao 提供**@TableShard** 注解用以配置分表策略。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TableShard {

    String shardTable();

    String[] shardColumns(); //支持多字段分表

    String separator() default "_";

    int shardCount();
}

magic-dao 使用提供了默认的分表处理器DefaultTableShardHandler用以根据分表策略计算实际表名。

public class DefaultTableShardHandler implements TableShardHandler {

@Override
public String getRealTableName(TableShardStrategy shardStrategy, Object... columnValues) {
    if(shardStrategy == null) {
        throw new RuntimeException("Failed to get actual table name, caused by tableShardStrategy is null!");
    }
    if(columnValues == null || columnValues.length < 1) {
        throw new RuntimeException("Failed to get actual table name, caused by columnValue is empty or null!");
    }

    StringBuilder sb = new StringBuilder();
    for(Object o : columnValues) {
        sb.append(String.valueOf(o));
    }
    int tableIndex = HashSlotUtil.getSlot(sb.toString(), shardStrategy.getShardCount());
    return shardStrategy.getShardTable() + shardStrategy.getSeparator() + ((tableIndex < 10) ? "0" + tableIndex : String.valueOf(tableIndex));
}

}

使用方法:

  • 单表分表访问

    实现单表分表访问非常简单,只需通过**@TableShard** 在PO对象上配置分表策略即可。

      @TableShard(shardTable = "mc_orders", shardCount = 32, shardColumn = "user_id", separator = "_")
      public class OrderPo implements Serializable {
    
      	@Key(column = "order_id")
      	private Long orderId;
    
      	@Column(value = "user_id")
      	private Long userId;
    
      	@Column(value = "create_time")
      	private Date createTime;
    
      	...
    
      	... <省略getXxx和setXxx方法>
    
      }
    
  • 联合查询分表访问

    尽量避免在代码中对子表(分表)进行联合查询,可以通过多次访问数据库并在应用层中进行整合。

  • 自定义分表处理器

    magic-dao 默认使用DefaultTableShardHandler 读取分表策略并根据shard字段的值利用JedisHashSlot 算法(redis cluster中key定位算法)计算实际表名,所以默认情况下不支持auto-increment字段(自增长字段在insert前无法知晓具体值),不过开发人员可以对该种情况实现自己的分表逻辑(如随机)。

    magic-dao为开发人员预留了自定义分表逻辑的空间,只需实现TableShardHandler 接口,并在Spring容器中将该handler实例注入到对应的dao实例中即可。

    public class MyTableShardHandler implements TableShardHandler {
        @Override
        public String getShardTableName(TableShardStrategy shardStrategy, Object... columnValue) {
    
    		//实现自己的分表逻辑
    
    		return xxx;
    	}
    }
    

    ...

    <bean id="MyShardHandler" class="test.pers.zr.magic.dao.core.action.MyTableShardHandler" />
    
    <bean id="appDao" class="demo.pers.zr.magic.dao.app.MagicAppDaoImpl" >
    	<property name="magicDataSource" ref="multiDataSource" />
    	<property name="tableShardHandler" ref="MyShardHandler" />
    </bean>
    

###3.7 单表动态查询 ### 在介绍单表动态查询具体方法前,请先看MagicDao 提供的针对单表查询接口:

//根据动态条件查询
public List<ENTITY> query(Matcher...conditions);

//根据动态条件查询并对结果排序
public List<ENTITY> query(List<Order> orders, Matcher...conditions);

//根据动态条件查询并分页
public List<ENTITY> query(Page page, Matcher...conditions);

//根据动态条件查询并对结果排序和分页
public List<ENTITY> query(Page page, List<Order> orders, Matcher...conditions);

//根据动态条件查询总数
public Long getCount(Matcher...conditions);

magic-dao 通过Matcher 接口来抽象各类型的查询条件,具体如下:

  • EqualsMatcher

    表示 column = xxx

  • NotEqualsMatcher

    表示 column != xxx

  • GreaterMatcher

    表示 column > xxx

  • GreaterOrEqualsMatcher

    表示 column >= xxx

  • LessMatcher

    表示 column < xxx

  • LessOrEqualsMatcher

    表示 column <= xxx

  • InMatcher

    表示 column in (xxx, yyy, ... zzz)

  • LikeMatcher

    表示 column like %xxx%

  • LeftLikeMatcher

    表示 column like xxx%

  • RightLikeMatcher

    表示 column like %xxx

  • BetweenAndMatcher

    表示 column between XXX and yyyy

熟悉了上述的动态查询和Matcher接口后,动态查询将会异常简单:

比如,查询指定用户的所有订单列表

public List<UserOrder> getUserOrderList(Long userId) {

	Matcher userMatcher = new EqualsMatcher("user_id", userId);

	return userOrderDao.query(userMatcher);

}

查询指定用户的所有订单列表并按时间倒叙排序

public List<UserOrder> getUserOrderList(Long userId) {

	Matcher userMatcher = new EqualsMatcher("user_id", userId);

	List<Order> orders = new ArrayList<Order>();
	Order order = new Order("create_time", OrderType.DESC);
	orders.add(order);

	return userOrderDao.query(orders, userMatcher);

}

分页查询用户的订单列表

public List<UserOrder> getUserOrderList(Long userId, int pageNo, int pageSize) {

	Matcher userMatcher = new EqualsMatcher("user_id", userId);

	Page page = new Page(pageNo, pageSize);

	return userOrderDao.query(page, userMatcher);

}

###3.8 多表联合查询 ### 复杂查询需要开发人员自己写SQL。只需要注意以下几点即可:

1、JdbcTemplate获取方式:

JdbcTemplate jdbcTemplate = magicDataSource.getJdbcTemplate(ActionMode.QUERY);

2、RowMapper获取方式:

//如果目标Mapper为当前Dao类的默认mapper,直接通过this.rowMapper即可访问

//如果需要获取别的Dao类中的mapper,可以通过MapperContextHolder获取
RowMapper rowMapper = MapperContextHolder.getRowMapper(xxxx.class);

###3.9 效率### 由于Spring单例模式的lazy-init属性默认值为false,即容器启动时,所有的Dao实例即被创建,在创建过程中,父类MagicGenericDao的默认构造器将被调用,用以扫描并初始化当前Dao所依赖的Po对象与表的映射关系以及Po字段与setXxx()和getXxx()的映射关系。所以,Spring容器在启动时便已经将所有的注解和映射关系都已经解析完毕,不用太担心效率问题。

TODO

分库

magic-dao's People

Contributors

rongzhu8888 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.