From the previous chapter [Mastering the Fourth chapter of spring Framework]

Spring data source automatic configuration

Working with databases is undoubtedly the most frequent part of a programmer’s daily work. A lot of job candidates who just graduated from college are confident. It is true that we are in an era of flying wheels, and many things have been done by the framework. Writing code is more like building blocks than building a house. We don’t need a brick house. That’s too expensive. But since we are building blocks, we need to know the structure of each block. That’s how you build a simple, solid house. Let’s learn how to work with the spring-data-JDBC module for database operations. To better illustrate, let’s do a simple example.

When the program starts, it inserts a data entry into the coffee table. So here’s the question. Data source, connection pool. Yes, they are all automatically configured by Spring. First let’s look at data source auto-configuration.

DataSourceProperties implements InitializingBean, whose afterPropertiesSet method automatically determines the embedded database type based on the currently loaded class.

DataSourceProperties can generate a DataSource to builder DataSourceBuilder, mainly is to set up drive the class, the connection url, user name and password and data source type. Here’s the point. What does the created DataSource do? We found that the DataSource package is tomcat-JDBC, indicating that the matter has been assigned to tomcat-JDBC.

tomcat-jdbc

It is an alternative to Apache Commons DBCP, and the website is full of benefits: tomcat.apache.org/tomcat-7.0-…

  1. Supports high concurrency.
  2. You can customize interceptors
  3. A high performance
  4. Support for JMX
  5. Very simple:tomcat-jdbcThere are only eight core files, most importantlyConnectionPoolclass
  6. More intelligent idle connection handling
  7. You can get it asynchronouslyConnection:Future<Connection>
  8. Custom connection drop timing: whether the connection pool is full or the connection usage has timed out.

The forehead… He doesn’t say, we only believe in the source code.

Initialization of the connection pool

Let’s set the breakpoint in pCreatePool of DataSourceProxy and look at its call stack.

With the first Connection fetched from the data source, a ConnectionPool is created. The ConnectionPool init method is used to create the ConnectionPool.

protected void init(PoolConfiguration properties) throws SQLException {
        poolProperties = properties;
        // Check the connection pool configuration
        checkPoolConfiguration(properties);
        // Initialize the busy queue used to store connections
        busy = new LinkedBlockingQueue<>();
        // Initialize the free queue for the connection
        idle = new FairBlockingQueue<>();
        // Initialize scheduled tasks: The connection pool health check is performed every 5000 milliseconds.
        initializePoolCleaner(properties);

        // If JMX is enabled, register mbeans
        if (this.getPoolProperties().isJmxEnabled()) createMBean();
  
        // Create 10 connections.
        PooledConnection[] initialPool = new PooledConnection[poolProperties.getInitialSize()];
        try {
            for (int i = 0; i < initialPool.length; i++) {
                // Create java.sql.Connection one by one using the org.h2.driver. connect method and add it to the busy queue.
                initialPool[i] = this.borrowConnection(0.null.null); //don't wait, should be no contention
            } //for

        } catch (SQLException x) {
            log.error("Unable to create initial connections of pool.", x);
            if(! poolProperties.isIgnoreExceptionOnPreLoad()) {if(jmxPool! =null) jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_INIT, getStackTrace(x));
                close(true);
                throwx; }}finally {
            //return the members as idle to the pool
            for (int i = 0; i < initialPool.length; i++) {
                if(initialPool[i] ! =null) {
                    // Return the 10 connections to idle queue one by one
                    try {this.returnConnection(initialPool[i]); }catch(Exception x){/*NOOP*/}}//end if
            } //for
        } //catch

        closed = false;
}
Copy the code

Found through source code, borrowConnection, who to borrow? Looking for idle to borrow. If the connection is insufficient, a new connection will be created internally. Of course, the borrowing process will still be inevitable. So here’s the question.

When do I get it back

That question is on hold. This will be explained later when dealing with transactions.

Can I keep it

ConnectionPool borrowConnection(int wait, String username, String password) It gets it from Idle in a non-blocking way, returns it if it gets it, and returns it if it doesn’t. Check if the current number of connections exceeds maxActive. Default is 100. If not, create one. If it exceeds. Block for the specified timeout. This timeout is set to maxWait30 seconds by default if wait passes -1. And if you don’t get a connection in 30 seconds. We’ll have to expose ourselves.

Will the connection be closed if not used for a long time

The Tomcat connection pool has a configuration item called maxAge. It means:

Connection hold time in milliseconds. When a connection is about to return to the pool, the pool checks to see if the condition now time-when- Connected > maxAge has been met, and if so, closes the connection and does not return it to the pool. The default value is 0, which means that the connection will remain open and no age checking will be performed when the connection is returned to the pool.

PooledConnection has an isMaxAgeExpired method to check if the maximum hold time has been exceeded. ConectionPool has a reconnectIfExpired method that checks that if a connection exceeds its maximum hold time, it is reconnected, that is, first disconnected, then connected. ReconnectIfExpired is executed when the connection is returned, so if maxAge is set it may trigger a reconnection operation.

There is also a configuration called maxIdle. It means:

(integer value) The maximum number of connections the pool should keep at all times. The default is maxActive:100. Periodically check the free connection (if you enable this feature), the retention time than minEvictableIdleTimeMillis idle connections will be released.

When a connection is returned, if the size of the idle queue exceeds this threshold, the current connection to be returned is released. That is, close the connection.

With maxIdle, there must be minIdle, which means the minimum number of connections a pool should keep at all times

Regularly cleaned task PoolCleaner found that the size of the idle queue if more than this value, will check each connection, the current time minus the last touched, if more than minEvictableIdleTimeMillis, connection is released. Note: this configuration item does not have maxXXX.

Tomcat gives us a number of configuration items that we can flexibly change depending on the actual situation. However, we usually set initialSize, minIdle and maxIdle to the same size on the actual line. Why is that? The reason is simple. Let’s say one day of traffic, except at peak times. 50 connections will do. Then these three values are set to 50, and the number of connections remains roughly the same. Don’t connect and disconect frequently, because connecting is also time-consuming. If you spend all your time on it, won’t it interfere with the right business? Once in a while there’s a little spike in traffic, but that’s okay, and then the connection count goes back up to 50. So what’s this value? I think if you don’t go above this value for most of the day, then it’s this value. By analogy, in fact, many connection pool configuration can refer to this routine.

Monitor the ConnectionPool using JMX

From the DataSource inheritance, we see that it is a bean that can be managed through JMX. But you have to turn it on before you can use it.

spring.datasource.tomcat.jmx-enabled=true
Copy the code

We can then view it using the JMX client JConsole. You can not only view indicators of interest, but also perform some operations. Even better, a notification will be sent to the JMX client if the connection pool fails to get a connection. If you write your own code to implement the client, you can email the alarm.

Spring declarative transactions

Reference: blog.csdn.net/qq_36882793…

Origin of declarative transactions

This is an easy question to answer, as transaction processing is one of the typical application scenarios mentioned earlier in the introduction of the AOP programming paradigm. Declarative transactions can be said to be the origin of the AOP programming paradigm. I have to say that the advent of declarative transactions has greatly facilitated transaction management by programmers. Rather, the advent of AOP has greatly facilitated code reuse by programmers. Let’s start with a very simple example. Take a look at how Spring implements declarative transactions. The following defines a CoffeeOrderService to simulate saving two cups of coffee: coffee1 coffee2. It’s that simple.

@Service
public class CoffeeService {

    @Autowired
    private CoffeeRepository coffeeRepository;

    @Transactional(rollbackFor = RuntimeException.class)
    public void save(Coffee coffee1, Coffee coffee2) {
        coffeeRepository.save(coffee1);
        System.out.println(coffeeRepository.findAll());
        coffeeRepository.save(coffee2);
        System.out.println(coffeeRepository.findAll());
        throw new RuntimeException("Hang up"); }}Copy the code

Using the @Transactional annotation, we can ensure that the Save method opens the transaction at the beginning of execution and either commits or rolls back the transaction at the end. The reader can easily imagine that this must be using Spring’s AOP technology. Speaking of AOP, it is natural to think that cglib dynamic proxies must be used here, since there is no interface. And the actual intercepting must be handed in to an implementation class for the MethodInterceptor. The following diagram briefly illustrates how Spring automatic configuration provides a transaction interceptor for transaction management and how to apply this interceptor to classes marked Transactional.

Spring routine:

  1. noticeadviceYou don’t just shove it inProxyFactoryThrough consultantsadvisorDynamically acquired.
  2. Decide if the consultant wants to intervenecoffeeOrderServiceThe core code is the following, depending on whether the target class is availableTransactionAttributeTo screen the candidatesconsultant. And gettxAttrThe first requirement is that there must be a target method@TransactionalAnnotation.
@Override
public boolean matches(Method method, Class
        targetClass) {
		TransactionAttributeSource tas = getTransactionAttributeSource();
		return (tas == null|| tas.getTransactionAttribute(method, targetClass) ! =null);
}

/ / from SpringTransactionAnnotationParser
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
		AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
				element, Transactional.class, false.false);
		if(attributes ! =null) {
			return parseTransactionAnnotation(attributes);
		}
		else {
			return null; }}Copy the code

We clarified the mechanics of dynamic proxies. Here’s how the TransactionInterceptor does transaction management. Note: The source code for invokeWithinTransaction posted below is an abridged version. I have omitted code that would not be executed by default.

protected Object invokeWithinTransaction(Method method, @NullableClass<? > targetClass,final InvocationCallback invocation) throws Throwable {

        // If the transaction attribute is null, the method is non-transactional.
        TransactionAttributeSource tas = getTransactionAttributeSource();
        finalTransactionAttribute txAttr = (tas ! =null ? tas.getTransactionAttribute(method, targetClass) : null);
        final TransactionManager tm = determineTransactionManager(txAttr);
        PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
        TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
        Object retVal;
        try {
            // This is an around advice: Invoke the next interceptor in the chain.
            // This will normally result in a target object being invoked.
            retVal = invocation.proceedWithInvocation();
        } catch (Throwable ex) {
            // target invocation exception
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        } finally {
            cleanupTransactionInfo(txInfo);
        }
        commitTransactionAfterReturning(txInfo);
        return retVal;
}
Copy the code
  1. Gets the basic properties of the transactionTransactionAttributeInclude:
  2. To obtainTransaction managerHere is the default transaction manager,JdbcTransactionManagerBecause I’m usingspring-data-jdbcIf you are usingspring-data-jpaThat isJpaTransactionManager. Of the transaction managerbeanisDataSourceTransactionManagerAutoConfigurationThis configuration class is introduced.

  1. createTransactionIfNecessary, which literally means create transactions if needed. Let’s take a look at its return value.TransactionInfo

It is a further encapsulation of TransactionStatus, TransactionAttribute, TransactionManager, and other attributes. Let’s first look at TransactionStatus, which is actually DefaultTransactionStatus, which encapsulates the transaction state.

As you can see, it encapsulates the database connection. And save point information. So here’s the question.

When was the connection fetched from the connection pool

In the above example I printed the SQL using the JDBC profileSQL function like this.

SET autocommit=0;
insert into coffee(name,price) values('latte'.10);
select * from coffee;
insert into coffee(name,price) values('American'.20);
select * from coffee;
rollback;
SET autocommit=1;
Copy the code

Let’s look at the call stack that starts a transaction.

This obtainDataSource retrieves the tomcat-JDBC data source. Since this is the first time I’m trying to connect to the database, the connection pool is initialized. There are a few special remarks to be made here.

  • AbstractPlatformTransactionManager#getTransactioncalldoGetTransactionCalled when the transaction object is builtTransactionSynchronizationManager.getResource(obtainDataSource())Getting the databaseConnection holder. This is obtained from the nameresourcestheThreadLocalWhere the key is the current data sourceConnection holder. Since this is the first time to start a transaction, there is obviously no such thing. So null is returned. The next step in determining whether a transaction has been started is by including a connection holder (ConnectionHolder). It’s not on in the example. soisExistingTransactionReturn tofalse
  • Next determine the isolation level, if yesPROPAGATION_MANDATORY, then self-destruct. Because of this isolation level, there must be an existing transaction. It certainly won’t explode here, because the default isolation level isPROPAGATION_REQUIRED. The isolation level is calledstartTransaction.
  • startTransactioncalldoBeginStart a transaction. Its job is essentially to get a connection from the data source and set it upautoCommitforfalseStart a transaction.
SavepointAllowed whytrue

When constructing a transaction object, if nestedTransactionAllowed of TransactionManager is true, then savepointAllowed is true, And at the time of initialization JdbcTransactionManager will call the superclass constructor of DataSourceTransactionManager, will nestedTransactionAllowed is set to true this tag is used to decide whether to support nested transactions.

How does SavePoint support nested transactions

Let’s start by reviewing the seven propagation behaviors of transactions defined by Spring

Propagation behavior meaning implementation
PROPAGATION_REQUIRED If there is no transaction, create a new one. If there is already one, join it. This is the default transaction propagation behavior slightly
PROPAGATION_SUPPORTS Current transactions are supported, and non-transactionally executed if there are none. slightly
PROPAGATION_MANDATORY Use the current transaction and throw an exception if there is no transaction currently. The transaction must exist (or not)Connection holder), otherwise an exception is thrown directly
PROPAGATION_REQUIRES_NEW Create a new transaction and suspend the current transaction if one exists. (A new transaction will be started, and if an existing transaction is running, the method will be suspended at runtime until the new transaction is committed or rolled back.) If a transaction already exists, replace the originalConnection holderMoved tosuspendedResources, then reobtains the connection and restarts the transaction.
PROPAGATION_NOT_SUPPORTED Performs the operation nontransactionally, suspending the current transaction if one exists. Suspending the current transaction is essentially removing the current transactionConnection holderRemove thesuspendedResourcesAnd then recover from itsuspendedResourcesTo move back toConnection holder
PROPAGATION_NEVER Executes nontransactionally, throwing an exception if a transaction currently exists. If a transaction exists (containsConnection holder) throws an exception directly
PROPAGATION_NESTED If a transaction currently exists, it is executed within a nested transaction. If there are no transactions currently, an operation similar to PROPAGATION_REQUIRED is performed. (If the outer transaction throws an exception, the inner transaction must be rolled back. Otherwise, the inner transaction does not affect the outer transaction.) See below for details

Savepoint implements propagation behavior, PROPAGATION_NESTED. AbstractPlatformTransactionManager handleExistingTransaction method, if is the nested transaction, first of all determine whether to allow the nested transaction, default is allowed, if not allowed, throw exceptions. Create and hold a security point, which can meet the requirements of nested transactions, namely the inner transaction rollback does not affect the outer transaction.

Let’s answer the question of when to return the database connection borrowed above. Obviously when the transaction commits. You can return the database connection. The following figure shows the call stack. After the transaction commits, there are some cleanup actions, such as releasing the connection.