Spring -- IV. JdbcTemplate and transaction control in spring

JdbcTemplate in Spring

JdbcTemplate overview

It is an object provided in the spring framework and a simple encapsulation of the original Jdbc API object. The spring framework provides us with many operation template classes.

  • Operation of relational data:
    • JdbcTemplate
    • HibernateTemplate
  • To operate nosql database:
    • RedisTemplate
  • Operation message queue:
    • JmsTemplate

Environment construction

jar package to be imported (version can be selected):

  • spring-jdbc-5.1.9.RELEASE.jar(JDBC)
  • spring-tx-5.1.9.RELEASE.jar (about transactions)
  • spring-context-5.1.9.RELEASE.jar(IOC)

Write configuration file

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
       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.xsd"> 
</beans>

Configure data source (Spring built-in data source)

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"></property>
    <property name="username" value="root"></property>
    <property name="password" value="password"></property>
</bean>

Configure JdbcTemplate

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

Operation example

//Entity class
public class UserManager {
    String username, password;
    double money;

    @Override
    public String toString() {
        return "UserManager{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", money=" + money +
                '}';
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}
//Get Spring container
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//Get bean object based on id
JdbcTemplate jt = (JdbcTemplate)ac.getBean("jdbcTemplate");
//query
//BeanPropertyRowMapper encapsulates each row of the query into an object and puts it into the collection
//Here, the queried username, password and money attributes are encapsulated into a UserManager class
List<UserManager> userManagers =  jt.query("select * from user",
                                           new BeanPropertyRowMapper<UserManager(UserManager.class));
//delete
jt.update("delete from user where username = ?","aaa");
//insert
jt.update("insert into user(username,password,money)values(?,?)","aaa","fff",5000);
//to update
jt.update("update user set money = money-? where username = ?",300,"aaa");

Using JdbcTemplate in DAO

The entity class is shown above

The first way

Define the JdbcTemplate in the DAO class

DAO class of UserManager

public class UserManagerDao implements IUserManagerDao {

    JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public UserManager FindUserByName(String username) {
        List<UserManager> userManagers = jdbcTemplate.query("select * from user where username=?",
                new BeanPropertyRowMapper<UserManager>(UserManager.class), username);
        return userManagers.isEmpty()?null:userManagers.get(0);
    }

    @Override
    public List<UserManager> FindAll() {
        return jdbcTemplate.query("select * from user",
                new BeanPropertyRowMapper<UserManager>(UserManager.class));
    }

    @Override
    public void UpdateUserByName(UserManager userManager) {
        jdbcTemplate.update("update user set password=?, money=? where username=?",
                userManager.getPassword(), userManager.getMoney(), userManager.getUsername());
    }
}

Configure the DAO class through the xml file:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"></property>
    <property name="username" value="root"></property>
    <property name="password" value="password"></property>
</bean>

<bean id="userManagerDao" class="com.dao.Impl.UserManagerDao">
    <property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

There is a small problem. When there are too many DAO classes, the following code will have a lot of duplicate code:

JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
}

The second way

Let DAO class inherit JdbcDaoSupport

JdbcDaoSupport is a class provided to us by the spring framework. A jdbctemplate object is defined in this class, which can be obtained and used directly. We only need to inherit this class. Moreover, at this time, we don't need to inject JDBC template into xml file. Instead, we can directly inject a dataSource object into UserManagerDao class (because JdbcDaoSupport class has the set method of dataSource, it will automatically create jdbctemplate with dataSource). You can see the xml file configuration method

public class UserManagerDao extends JdbcDaoSupport implements IUserManagerDao {
    @Override
    public UserManager FindUserByName(String username) {
        List<UserManager> userManagers = 
            super.getJdbcTemplate().query("select * from user where username=?",
                new BeanPropertyRowMapper<UserManager>(UserManager.class), username);
        return userManagers.isEmpty()?null:userManagers.get(0);
    }
    @Override
    public List<UserManager> FindAll() {
        return super.getJdbcTemplate().query("select * from user",
                new BeanPropertyRowMapper<UserManager>(UserManager.class));
    }
    @Override
    public void UpdateUserByName(UserManager userManager) {
        super.getJdbcTemplate().update("update user set password=?, money=? where username=?",
                userManager.getPassword(), userManager.getMoney(), userManager.getUsername());
    }
}
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"></property>
    <property name="username" value="root"></property>
    <property name="password" value="password"></property>
</bean>
<bean id="userManagerDao" class="com.dao.Impl.UserManagerDao">
    <property name="dataSource" ref="dataSource"></property>
</bean>

contrast

When using the second method, the code redundancy is indeed reduced. However, when using annotation configuration, because the JdbcDaoSupport class is a Spring class, we cannot modify it. Therefore, the second method can only be used when using xml file configuration, and the first method can be used when using annotation.

Transaction control in Spring

Overview of transaction control in Spring

Transaction control is to keep the transactions composed of a series of actions atomic, consistent, isolated and persistent.

  • Atomicity: a transaction is an atomic operation consisting of a series of actions. The atomicity of the transaction ensures that the action is either fully completed or completely ineffective.
  • Consistency: once the transaction is completed (whether successful or failed), the system must ensure that the business it models is in a consistent state, rather than partial completion and partial failure. In reality, data should not be destroyed.
  • Isolation: many transactions may process the same data at the same time, so each transaction should be isolated from other transactions to prevent data corruption.
  • Durability: once the transaction is completed, no matter what system error occurs, its result should not be affected, so it can recover from any system crash. Typically, the result of a transaction is written to persistent storage.

A classic example is bank transfer, which is divided into several steps: the transfer starts, the account deducts money, the account adds money, and the transfer ends. However, once the account has been deducted, some abnormal transfer behavior occurs in the system and cannot continue. If there is no transaction control, the transfer will be forcibly ended, and the transaction control will maintain its consistency, start the rollback transaction, and restore all information to the state before the transfer.

Spring's transaction control is based on AOP, which is divided into two types: declarative transaction control (i.e. implemented by configuration) and programmatic transaction control (i.e. implemented by programming).

Introduction to Spring transaction control API

PlatformTransactionManager

Transaction manager

TransactionStatus getTransaction();//Get the status information of the transaction
void commit();//Commit transaction
void rollback();//Rollback transaction

TransactionDefinition

Definition information object of transaction

String getName();//Get transaction object name
int getIsolationLevel();//Get transaction isolation level
int getPropagationBehavior();//Get transaction propagation behavior
int getTimeout();//Get transaction timeout
boolean isReadOnly();//Gets whether the transaction is read-only
  • Read write transactions: add, delete, and modify
  • Read only transactions: queries
Isolation level of transaction

Propagation behavior of transactions
  • REQUIRED: if there is no transaction at present, create a new transaction. If there is already a transaction, join it. General selection (default)
  • SUPPORTS: SUPPORTS the current transaction. If there is no transaction, it will be executed in a non transactional manner (no transaction)
  • MANDATORY: use the current transaction. If there is no transaction, an exception will be thrown
  • REQUERS_NEW: create a new transaction. If it is currently in a transaction, suspend the current transaction.
  • NOT_SUPPORTED: perform the operation in a non transactional manner. If there is a current transaction, suspend the current transaction
  • NEVER: run in non transaction mode. If there is a transaction, throw an exception
  • NESTED: if a transaction currently exists, it is executed within a NESTED transaction. If there is no current transaction, perform an operation similar to REQUIRED.
Timeout

The default value is - 1 and there is no timeout limit. If yes, set it in seconds.

Is it a read-only transaction

It is recommended that the query be set to read-only.

TransactionStatus

Describes the state information of fifteen objects at a certain point in time

void flush();//Refresh transaction
boolean hasSavepoint();//Gets whether a storage point exists
boolean isCompleted();//Gets whether the transaction is complete
boolean isNewTransaction();//Gets whether the transaction is a new transaction
boolean isRollbackOnly();//Gets whether the transaction is rolled back
void setRollbackOnly();//Set transaction rollback

Declarative transaction control based on XML

Environment construction

Set packaging method and package Guide

First set the packaging method as jar package and import jar package:

  • spring-jdbc
  • spring-tx
  • spring-context
  • aspectjweaver
  • Database driven
Create Spring configuration files and import constraints (you need to import the namespaces of tx and aop here)
<?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:aop="http://www.springframework.org/schema/aop" 
       xmlns:tx="http://www.springframework.org/schema/tx" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd 
                           http://www.springframework.org/schema/tx 
                           http://www.springframework.org/schema/tx/spring-tx.xsd 
                           http://www.springframework.org/schema/aop 
                           http://www.springframework.org/schema/aop/spring-aop.xsd"> 
</beans>
Prepare database and entity classes
create table account( 
    id int primary key auto_increment, 
    name varchar(40), 
    money float 
)character set utf8 collate utf8_general_ci;
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float money;
    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Float getMoney() {
        return money;
    }
    public void setMoney(Float money) {
        this.money = money;
    }
}
Prepare Dao layer interface and implementation class

Interface:

public interface IAccountDao {
    Account findAccountById(Integer id);
    Account findAccountByName(String name);
    void updateAccount(Account account);
}

Implementation class:

public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
    public Account findAccountById(Integer id) {
        List<Account> list =
                getJdbcTemplate().query("select * from account where id = ?",
                        new BeanPropertyRowMapper<Account>(Account.class), id);
        return list.isEmpty()?null:list.get(0);
    }
    public Account findAccountByName(String name) {
        List<Account> list =
                getJdbcTemplate().query("select * from account where name = ?",
                        new BeanPropertyRowMapper<Account>(Account.class), name);
        if (list.isEmpty()){
            return null;
        }
        if (list.size()>1){
            throw new RuntimeException("The result is not unique");
        }
        return list.get(0);
    }
    public void updateAccount(Account account) {
        getJdbcTemplate().update("update account set money = ? where id = ?",
                account.getMoney(), account.getId());
    }
}
Prepare business layer interfaces and implementation classes

Interface:

public interface IAccountService {
    Account findAccountById(Integer id);
    void transfer(String sourceName, String targetName, Float money);
}

Implementation class:

public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

    public void transfer(String sourceName, String targetName, Float money) {
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);

        source.setMoney(source.getMoney()-money);
        target.setMoney(target.getMoney()+money);

        accountDao.updateAccount(source);
        accountDao.updateAccount(target);
    }
}
Configure the data source, business layer and persistence layer in the configuration file
<!--Configure data source-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"></property>
    <property name="username" value="root"></property>
    <property name="password" value="password"></property>
</bean>

<!--to configure Dao-->
<bean id="accountDao" class="com.dao.Impl.AccountDaoImpl">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--to configure service-->
<bean id="accountService" class="com.service.Impl.AccountServiceImpl">
    <property name="accountDao" ref="accountDao"></property>
</bean>

XML based configuration steps

Configure transaction manager
<!--Configure transaction manager-->
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
Configure notification and properties of transactions
<!--Configure notifications for transactions-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--Configure the properties of the transaction
        name Is the name of the core method of the business. Wildcards can be used*replace
        find*That is to say find The method at the beginning has higher priority than using wildcards alone
    -->
    <tx:attributes>
        <tx:method name="*" read-only="false" propagation="REQUIRED"/>
        <tx:method name="find*" read-only="true" propagation="SUPPORTS"></tx:method>
    </tx:attributes>
</tx:advice>
  • Read only: whether it is a read-only transaction. The default is false and not read-only.
  • Isolation: Specifies the isolation level of the transaction. The default value is to use the default isolation level of the database.
  • Propagation: Specifies the propagation behavior of the transaction.
  • Timeout: Specifies the timeout. The default value is: - 1. Never time out.
  • Rollback for: used to specify an exception. When the exception is generated during execution, the transaction is rolled back. If other exceptions are generated, the transaction will not be rolled back. There is no default value and any exceptions are rolled back.
  • No rollback for: used to specify an exception. When this exception occurs, the transaction will not be rolled back. When other exceptions occur, the transaction will be rolled back. There is no default value and any exceptions are rolled back.
Configure AOP pointcut expressions and the corresponding relationship between pointcut expressions and transaction notifications
<aop:config>
    <aop:pointcut id="pt1" expression="execution(* com.service.Impl.*.*(..))"/>
    <!--Establish the corresponding relationship between pointcut expression and transaction notification-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>

Test code

public static void main(String[] args) {
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    IAccountService accountService = (IAccountService) ac.getBean("accountService");
    accountService.transfer("a", "b", 100f);
}

Annotation based declarative transaction control

Environment construction

Set packaging method and package Guide

Same as XML based configuration

Create spring's configuration file, import constraints, and configure scanned packages
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--Configure packages to scan-->
    <context:component-scan base-package="com"></context:component-scan>
Create database tables and entity classes

Same as XML based configuration

Create Dao interfaces and implementation classes and use annotations to let spring manage

Note: when using annotations, you can no longer inherit JdbcDaoSupport. You must manually create the JdbcTemplate class and inject it with @ autowritten, and also in the bean Configure the JdbcTemplate in the XML file.

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public Account findAccountById(Integer id) {
        List<Account> list =
                jdbcTemplate.query("select * from account where id = ?",
                        new BeanPropertyRowMapper<Account>(Account.class), id);
        return list.isEmpty()?null:list.get(0);
    }

    public Account findAccountByName(String name) {
        List<Account> list =
                jdbcTemplate.query("select * from account where name = ?",
                        new BeanPropertyRowMapper<Account>(Account.class), name);
        if (list.isEmpty()){
            return null;
        }
        if (list.size()>1){
            throw new RuntimeException("The result is not unique");
        }
        return list.get(0);
    }

    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set money = ? where id = ?",
                account.getMoney(), account.getId());
    }
}
Create business layer interfaces and implementation classes and use annotations to let spring manage

Remove the set method and use autowritten automatic injection

@Service("accountService")
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;

    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

    public void transfer(String sourceName, String targetName, Float money) {
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);

        source.setMoney(source.getMoney()-money);
        target.setMoney(target.getMoney()+money);

        accountDao.updateAccount(source);
        accountDao.updateAccount(target);
    }
}
Other xml configurations
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--Configure data source-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"></property>
    <property name="username" value="root"></property>
    <property name="password" value="password"></property>
</bean>

Annotation based configuration steps

Configure transaction manager
<!--Configure transaction manager-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
Use @ Transactional annotation in the business layer
@Service("accountService")
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;

    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public void transfer(String sourceName, String targetName, Float money) {
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);

        source.setMoney(source.getMoney()-money);
        target.setMoney(target.getMoney()+money);

        accountDao.updateAccount(source);
        accountDao.updateAccount(target);
    }
}

The attribute of this annotation has the same meaning as the attribute in xml. This annotation can appear on interfaces, classes and methods.

  • It appears on the interface, indicating that all implementation classes of the interface have transaction support.
  • Appears on the class, indicating that all methods in the class have transaction support
  • Appears on the method, indicating that the method has transaction support.

Priority of the above three positions: method > class > interface

Enable spring's support for annotation transactions in the configuration file
<!--open spring Support for annotation transactions-->
<tx:annotation-driven transaction-manager="transactionManager"/>

Test code

ditto

Some new features of spring 5

JDK upgrade

spring5.0 released its GA (Universal) version in September 2017. This version is written based on jdk8, so the following versions of jdk8 cannot be used. At the same time, it can be compatible with jdk9 version. tomcat version requires 8.5 and above.

jdk1. The running time of version 8 (JDK8) is as follows:

When switching to jdk1 After version 7, the running time is as follows:

Objects created in a frame are usually created using reflection.

Update of core container

Spring Framework 5.0 now supports candidate component indexes as an alternative to classpath scanning. This feature has been added to the classpath scanner to simplify the step of adding candidate component identification.

The application build task can define its own meta-inf / Spring. For the current project Components file. At compile time, the source model is self-contained, and JPA entities and Spring components are marked.

Reading entities from the index instead of scanning the classpath is no significant difference for small projects with less than 200 classes. However, it has a great impact on large projects. The cost of loading component indexes is lower. Therefore, as the number of classes increases, the start time of index reading will remain unchanged.

The cost of loading component indexes is cheap. Therefore, when the number of classes continues to grow and the start-up time of index building can still maintain a constant, but for component scanning, the start-up time will increase significantly.

What this means for our developers in large Spring projects is that the startup time of the application will be greatly reduced. Although 20 or 30 seconds may seem like nothing, if you have to board hundreds of times a day, it's enough for you. Using component index can help you live more efficiently every day.

You can do it in Spring Jira Learn more about component indexes on.

end

There's probably only so much about the Spring framework...

Tags: Java Spring

Posted by JAM on Sat, 16 Apr 2022 07:41:19 +0930