This article is excerpted from Spring 5 core principles
Before reading this article, please read the following:
30 classes of custom ORM for handwritten Spring core principles (Part 1) (6)
Custom ORM of 30 classes handwritten Spring core principles (Part 2) (7)
4. Underlying principle of dynamic data source switching
Here is a brief introduction to the basic principle of AbstractRoutingDataSource. The function of data source switching is to customize a class to extend the AbstractRoutingDataSource abstract class. In fact, it is equivalent to the routing intermediary of the data source. It can switch to the corresponding DataSource according to the corresponding key value when the project is running. Let's take a look at the source code of the AbstractRoutingDataSource class:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { /*Only partial codes are listed*/ @Nullable private Map<Object, Object> targetDataSources; @Nullable private Object defaultTargetDataSource; private boolean lenientFallback = true; private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); @Nullable private Map<Object, DataSource> resolvedDataSources; @Nullable private DataSource resolvedDefaultDataSource; ... public Connection getConnection() throws SQLException { return this.determineTargetDataSource().getConnection(); } public Connection getConnection(String username, String password) throws SQLException { return this.determineTargetDataSource().getConnection(username, password); } ... protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = this.determineCurrentLookupKey(); DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey); if(dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if(dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } else { return dataSource; } } @Nullable protected abstract Object determineCurrentLookupKey(); }
As you can see, the AbstractRoutingDataSource class inherits the AbstractDataSource class and implements InitializingBean. The getConnection() method of the AbstractRoutingDataSource class calls this method of determineTargetDataSource(). Here, we will focus on the code of the determinetargetdatasource () method, which uses the determineCurrentLookupKey() method. It is an abstract method of the AbstractRoutingDataSource class and a method to implement the data source switching extension. The return value of this method is the key value of the DataSource to be used in the project. After obtaining the key value, you can get the corresponding DataSource in resolvedDataSource. If the DataSource corresponding to the key cannot be found, the default DataSource will be used.
When the user-defined class extends the AbstractRoutingDataSource class, it is necessary to override the determineCurrentLookupKey() method to switch data sources.
4.1 DynamicDataSource
The DynamicDataSource class encapsulates a custom data source and inherits the data source dynamic router of the AbstractRoutingDataSource class of the native Spring.
package javax.core.common.jdbc.datasource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * Dynamic data source */ public class DynamicDataSource extends AbstractRoutingDataSource { private DynamicDataSourceEntry dataSourceEntry; @Override protected Object determineCurrentLookupKey() { return this.dataSourceEntry.get(); } public void setDataSourceEntry(DynamicDataSourceEntry dataSourceEntry) { this.dataSourceEntry = dataSourceEntry; } public DynamicDataSourceEntry getDataSourceEntry(){ return this.dataSourceEntry; } }
4.2 DynamicDataSourceEntry
The DynamicDataSourceEntry class implements the operation function of the data source. The code is as follows:
package javax.core.common.jdbc.datasource; import org.aspectj.lang.JoinPoint; /** * Dynamically switch data sources */ public class DynamicDataSourceEntry { //Default data source public final static String DEFAULT_SOURCE = null; private final static ThreadLocal<String> local = new ThreadLocal<String>(); /** * Clear data source */ public void clear() { local.remove(); } /** * Gets the name of the data source currently in use * * @return String */ public String get() { return local.get(); } /** * Restore the data source of the specified slice * * @param joinPoint */ public void restore(JoinPoint join) { local.set(DEFAULT_SOURCE); } /** * Restore the data source of the current slice */ public void restore() { local.set(DEFAULT_SOURCE); } /** * Set a data source with a known name * * @param dataSource */ public void set(String source) { local.set(source); } /** * Dynamically set the data source according to the year * @param year */ public void set(int year) { local.set("DB_" + year); } }
5. Operation effect demonstration
5.1 create Member entity class
The code for creating the Member entity class is as follows:
package com.tom.orm.demo.entity; import lombok.Data; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import java.io.Serializable; @Entity @Table(name="t_member") @Data public class Member implements Serializable { @Id private Long id; private String name; private String addr; private Integer age; @Override public String toString() { return "Member{" + "id=" + id + ", name='" + name + '\'' + ", addr='" + addr + '\'' + ", age=" + age + '}'; } }
5.2 create Order entity class
The code for creating the Order entity class is as follows:
package com.tom.orm.demo.entity; import lombok.Data; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; import java.io.Serializable; @Entity @Table(name="t_order") @Data public class Order implements Serializable { private Long id; @Column(name="mid") private Long memberId; private String detail; private Long createTime; private String createTimeFmt; @Override public String toString() { return "Order{" + "id=" + id + ", memberId=" + memberId + ", detail='" + detail + '\'' + ", createTime=" + createTime + ", createTimeFmt='" + createTimeFmt + '\'' + '}'; } }
5.3 create MemberDao
The code for creating MemberDao is as follows:
package com.tom.orm.demo.dao; import com.tom.orm.demo.entity.Member; import com.tom.orm.framework.BaseDaoSupport; import com.tom.orm.framework.QueryRule; import org.springframework.stereotype.Repository; import javax.annotation.Resource; import javax.sql.DataSource; import java.util.List; @Repository public class MemberDao extends BaseDaoSupport<Member,Long> { @Override protected String getPKColumn() { return "id"; } @Resource(name="dataSource") public void setDataSource(DataSource dataSource){ super.setDataSourceReadOnly(dataSource); super.setDataSourceWrite(dataSource); } public List<Member> selectAll() throws Exception{ QueryRule queryRule = QueryRule.getInstance(); queryRule.andLike("name","Tom%"); return super.select(queryRule); } }
5.4 creating OrderDao
The code for creating OrderDao is as follows:
package com.tom.orm.demo.dao; import com.tom.orm.demo.entity.Order; import com.tom.orm.framework.BaseDaoSupport; import org.springframework.stereotype.Repository; import javax.annotation.Resource; import javax.core.common.jdbc.datasource.DynamicDataSource; import javax.sql.DataSource; import java.text.SimpleDateFormat; import java.util.Date; @Repository public class OrderDao extends BaseDaoSupport<Order, Long> { private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy"); private SimpleDateFormat fullDataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private DynamicDataSource dataSource; @Override protected String getPKColumn() {return "id";} @Resource(name="dynamicDataSource") public void setDataSource(DataSource dataSource) { this.dataSource = (DynamicDataSource)dataSource; this.setDataSourceReadOnly(dataSource); this.setDataSourceWrite(dataSource); } /** * @throws Exception * */ public boolean insertOne(Order order) throws Exception{ //Convention over configuration Date date = null; if(order.getCreateTime() == null){ date = new Date(); order.setCreateTime(date.getTime()); }else { date = new Date(order.getCreateTime()); } Integer dbRouter = Integer.valueOf(yearFormat.format(date)); System.out.println("Automatically assign to[ DB_" + dbRouter + "]data source"); this.dataSource.getDataSourceEntry().set(dbRouter); order.setCreateTimeFmt(fullDataFormat.format(date)); Long orderId = super.insertAndReturnId(order); order.setId(orderId); return orderId > 0; } }
5.5 modify dB Properties file
Modify dB The properties file code is as follows:
#sysbase database mysql config #mysql.jdbc.driverClassName=com.mysql.jdbc.Driver #mysql.jdbc.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-demo?characterEncoding=UTF-8&rewriteBatchedStatements=true #mysql.jdbc.username=root #mysql.jdbc.password=123456 db2018.mysql.jdbc.driverClassName=com.mysql.jdbc.Driver db2018.mysql.jdbc.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-2018?characterEncoding=UTF-8&rewriteBatchedStatements=true db2018.mysql.jdbc.username=root db2018.mysql.jdbc.password=123456 db2019.mysql.jdbc.driverClassName=com.mysql.jdbc.Driver db2019.mysql.jdbc.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-2019?characterEncoding=UTF-8&rewriteBatchedStatements=true db2019.mysql.jdbc.username=root db2019.mysql.jdbc.password=123456 #alibaba druid config dbPool.initialSize=1 dbPool.minIdle=1 dbPool.maxActive=200 dbPool.maxWait=60000 dbPool.timeBetweenEvictionRunsMillis=60000 dbPool.minEvictableIdleTimeMillis=300000 dbPool.validationQuery=SELECT 'x' dbPool.testWhileIdle=true dbPool.testOnBorrow=false dbPool.testOnReturn=false dbPool.poolPreparedStatements=false dbPool.maxPoolPreparedStatementPerConnectionSize=20 dbPool.filters=stat,log4j,wall
5.6 modify application dB XML file
Modify application dB The XML file code is as follows:
<bean id="datasourcePool" abstract="true" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="initialSize" value="${dbPool.initialSize}" /> <property name="minIdle" value="${dbPool.minIdle}" /> <property name="maxActive" value="${dbPool.maxActive}" /> <property name="maxWait" value="${dbPool.maxWait}" /> <property name="timeBetweenEvictionRunsMillis" value="${dbPool.timeBetweenEvictionRunsMillis}" /> <property name="minEvictableIdleTimeMillis" value="${dbPool.minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${dbPool.validationQuery}" /> <property name="testWhileIdle" value="${dbPool.testWhileIdle}" /> <property name="testOnBorrow" value="${dbPool.testOnBorrow}" /> <property name="testOnReturn" value="${dbPool.testOnReturn}" /> <property name="poolPreparedStatements" value="${dbPool.poolPreparedStatements}" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="${dbPool.maxPoolPreparedStatementPerConnectionSize}" /> <property name="filters" value="${dbPool.filters}" /> </bean> <bean id="dataSource" parent="datasourcePool"> <property name="driverClassName" value="${db2019.mysql.jdbc.driverClassName}" /> <property name="url" value="${db2019.mysql.jdbc.url}" /> <property name="username" value="${db2019.mysql.jdbc.username}" /> <property name="password" value="${db2019.mysql.jdbc.password}" /> </bean> <bean id="dataSource2018" parent="datasourcePool"> <property name="driverClassName" value="${db2018.mysql.jdbc.driverClassName}" /> <property name="url" value="${db2018.mysql.jdbc.url}" /> <property name="username" value="${db2018.mysql.jdbc.username}" /> <property name="password" value="${db2018.mysql.jdbc.password}" /> </bean> <bean id="dynamicDataSourceEntry" class="javax.core.common.jdbc.datasource.DynamicDataSourceEntry" /> <bean id="dynamicDataSource" class="javax.core.common.jdbc.datasource.DynamicDataSource" > <property name="dataSourceEntry" ref="dynamicDataSourceEntry"></property> <property name="targetDataSources"> <map> <entry key="DB_2019" value-ref="dataSource"></entry> <entry key="DB_2018" value-ref="dataSource2018"></entry> </map> </property> <property name="defaultTargetDataSource" ref="dataSource" /> </bean>
5.7 writing test cases
Write the test case code as follows:
package com.tom.orm.test; import com.tom.orm.demo.dao.MemberDao; import com.tom.orm.demo.dao.OrderDao; import com.tom.orm.demo.entity.Member; import com.tom.orm.demo.entity.Order; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.List; @ContextConfiguration(locations = {"classpath:application-context.xml"}) @RunWith(SpringJUnit4ClassRunner.class) public class OrmTest { private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmdd"); @Autowired private MemberDao memberDao; @Autowired private OrderDao orderDao; @Test public void testSelectAllForMember(){ try { List<Member> result = memberDao.selectAll(); System.out.println(Arrays.toString(result.toArray())); } catch (Exception e) { e.printStackTrace(); } } @Test @Ignore public void testInsertMember(){ try { for (int age = 25; age < 35; age++) { Member member = new Member(); member.setAge(age); member.setName("Tom"); member.setAddr("Hunan Changsha"); memberDao.insert(member); } }catch (Exception e){ e.printStackTrace(); } } @Test // @Ignore public void testInsertOrder(){ try { Order order = new Order(); order.setMemberId(1L); order.setDetail("Historical order"); Date date = sdf.parse("20180201123456"); order.setCreateTime(date.getTime()); orderDao.insertOne(order); }catch (Exception e){ e.printStackTrace(); } } }
ORM means Object Relation Mapping. There are many ORM frameworks on the market, such as Hibernate, Spring JDBC, MyBatis and JPA. They all have object relation management mechanisms, such as one to many, many to many and one to one. The above ideas are for reference only. Interested partners can refer to the ideas provided in this article. The agreement is better than the configuration. First formulate the top-level interface and unify all the parameter return values, such as:
//List<?> Page<?> select(QueryRule queryRule) //The ID in Int delete(T entity) entity cannot be empty. If the ID is empty, other conditions cannot be empty. If it is empty, it will not be executed //Returnid insert (t entity) as long as entity is not equal to null //The ID in Int update(T entity) entity cannot be empty. If the ID is empty, other conditions cannot be empty. If it is empty, it will not be executed
Then expand on this basis, including one package based on Spring JDBC, one package based on Redis, one package based on MongoDB, one package based on ElasticSearch, one package based on Hive and one package based on HBase. This paper fully demonstrates the principle of self-developed ORM framework and the basic principle of dynamic data source switching, and understands the API Application of Spring JdbcTemplate. I hope that through the study of this chapter, "little partners" can have better ideas to solve problems and improve work efficiency in their daily work.
Pay attention to WeChat official account Tom bomb structure and reply to "Spring" to get the complete source code.
This article is the original of "Tom bomb architecture". Please indicate the source for reprint. Technology lies in sharing, I share my happiness! If you have any suggestions, you can also leave comments or private letters. Your support is the driving force for me to adhere to my creation. Focus on WeChat official account Tom structure, get more dry cargo!
It's not easy to be original. It's cool to insist. I've seen it here. Little partners remember to like, collect and watch it. Pay attention to it three times a button! If you think the content is too dry, you can share and forward it to your friends!