Spring学习笔记第四天,Spring中的JDBCTemplate、Spring基于AOP的事务控制、Spring中的事务控制

Spring 学习 day3 : AOP,Spring中JdbcTemplate的使用 1.AOP 1.1 什么是AOP 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方 式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切 阅读详情

1.Spring中的JDBCTemplate

1.1 Spring中JDBCTemplate的概述

在这里插入图片描述
spring中JDBCTemplate是对JDBC简单封装和 commons-dbutils 类似,在使用起来也很相似。它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。 spring 框架为我们提供了很多的操作模板类。

功能模板类
操作关系型数据的JdbcTemplate、HibernateTemplate
操作 nosql 数据库的RedisTemplate
操作消息队列的JmsTemplate

我们今天的主角在 spring-jdbc-5.0.2.RELEASE.jar 中,我们在导包的时候,除了要导入这个 jar 包外,还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。
我们先创建一个新的Module命名为day04_01jdbcTemplate,首先修改pom.xml导入项目需要的jar包。

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.itheima</groupId>
    <artifactId>day04_01jdbcTemplate</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency> <!--连接数据库-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency> <!--事务控制-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
    </dependencies>
</project>

然后我们准备一张数据库表,这个表我们之前的学习中已经创建过了,

DROP TABLE IF EXISTS `account`;
CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `money` float NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `account` VALUES (1, 'aaa', 800);
INSERT INTO `account` VALUES (2, 'bbb', 2100);

然后在创建com.itheima.domain包并创建这个表的实体类Account.java

public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float 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;
    }
    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

下面就来演示一下Spring中JDBCTemplate最简单的使用方式,在创建一个com.itheima.jdbctemplate包,并且在其中创建一个JdbcTemplateDemo1.java。

/**
 * JdbcTemplate 的最基本用法
 */
public class JdbcTemplateDemo1 {
    public static void main(String[] args) {
        // 准备数据源:Spring的内置数据源
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring");
        ds.setUsername("root");
        ds.setPassword("123456");

        //1.创建JdbcTemplate对象
        JdbcTemplate jt = new JdbcTemplate();
        //给jt设置数据源
        jt.setDataSource(ds);
        //2.执行操作
        jt.execute("insert into account(name, money) values ('ddd', 1000)");
    }
}

这个Module的完整结构如下。
在这里插入图片描述

我们先来插查看一下数据库中的数据如下。
在这里插入图片描述
然后我们来运行下这个main方法。运行完成之后我们在来看一下数据库表的变化。数据库表如下,说名运行成功了
在这里插入图片描述
我们在来看一下这个main方法里的代码,发现数据库连接的参数都是写死的,而且对象是new出来的,如果你学过前面的知识你会马上想到使用ioc来配置

1.2 JDBCTemplate在Spring IOC中的使用:

通过对上面的代码分析,我们可以对上面的代码进行一定的优化,下面我们就使用IOC来重新配置一下。首先在resources里新建一个bean.xml文件,来配置jdbcTemplate对象

<?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">
    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>

然后我们把JdbcTemplateDemo1.java拷贝一份,复制到和JdbcTemplateDemo1.java同一个包下命名为JdbcTemplateDemo2.java。

public class JdbcTemplateDemo2 {
    public static void main(String[] args) {
        //1.获取IOC容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.根据id获取bean对象
        JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
        //3.执行操作
        jt.execute("insert into account(name, money) values ('ccc', 2222)");
    }
}

然后我们来执行一个这个main方法,通过查看数据库表的变化,发现这样也是可以正常运行的。

1.3 JDBCTemplate的CRUD操作

下面我们就来学习一下JDBCTemplate的CRUD操作。我们把JdbcTemplateDemo2.java复制一份放到和JdbcTemplateDemo2.java相同的目录下命名为JdbcTemplateDemo3.java。CRUD的具体操作请看下面的代码。

/**
 * JdbcTemplate 的CRUD操作
 */
public class JdbcTemplateDemo3 {
    public static void main(String[] args) {
        //1.获取IOC容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.根据id获取bean对象
        JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
        //3.执行操作
        //保存
        // jt.update("insert into account(name, money) values (?,?)", "eee", 3333f);
        //更新
        //jt.update("update account set name = ?, money = ? where id = ?", "test", 4567f, 7);
        //删除
        //jt.update("delete from account where id = ?", 5);
        //查询所有,使用 我们自定义Account的封住策略
//        List<Account> accounts = jt.query("select * from account where money > ?;", new AccountRowMapper(), 1000f);
        //使用Spring为我们提供的封住策略
//        List<Account> accounts = jt.query("select * from account where money > ?;", new BeanPropertyRowMapper<Account>(Account.class), 1000f);
//        System.out.println(Arrays.toString(accounts));
        //查询一个
//        List<Account> accounts = jt.query("select * from account where id = ?;", new BeanPropertyRowMapper<Account>(Account.class), 1);
//        System.out.println(accounts.isEmpty() ? "没有内容" : accounts.get(0));
        //查询返回一行一列(使用聚合函数,但不加group by句子)
        Long count = jt.queryForObject("select count(*) from account where money > ?", Long.class, 1000f); // 避免返回值大于Integer的最大值,所以使用Long
        System.out.println(count);
    }
}

/**
 * 定义Account的封住策略
 */
class AccountRowMapper implements RowMapper<Account> {

    /**
     * 把结果集中的数据封装到Account中,然后Spring把每个Account加到集合中
     *
     * @param resultSet
     * @param i
     * @return
     * @throws SQLException
     */
    public Account mapRow(ResultSet resultSet, int i) throws SQLException {
        Account account = new Account();
//        account.setId(resultSet.getInt("id"));
//        account.setName(resultSet.getString("name"));
//        account.setMoney(resultSet.getFloat("money"));

        //使用反射设置属性值
        ResultSetMetaData rsmd = resultSet.getMetaData();
        // 通过ResultSetMetaData获取结果集中的列数
        int columnCount = rsmd.getColumnCount();
        for (int j = 0; j < columnCount; j++) {
            try {
                // 获取每个列的列名
                String columName = rsmd.getColumnName(j + 1);
                Field field = Account.class.getDeclaredField(columName);
                field.setAccessible(true);

                // 获取每个列的列值
                Object columValue = resultSet.getObject(j + 1);
                // field.set(account, columValue);
                field.set(account, resultSet.getObject(columName));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return account;
    }
}

1.4 JDBCTemplate在DAO中的使用

既然我们要说 JDBCTemplate在Dao中的使用,那么首先我们要在com.itheima.dao包里创建一个IAccountDao.java的接口

/**
 * 账户的持久层接口
 */
public interface IAccountDao {
    /**
     * 根据id查询账户
     *
     * @param accountId
     * @return
     */
    Account findAccountById(Integer accountId);

    /**
     * 根据名称查询账户
     *
     * @param accountName
     * @return
     */
    Account findAccountByName(String accountName);

    /**
     * 更新账户信息
     *
     * @param account
     */
    void updateAccount(Account account);
}

然后在com.itheima.dao.impl包里创建一个IAccountDao的实现类AccountDaoImpl.java

/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl implements IAccountDao {
    private JdbcTemplate jdbcTemplate;

    // 用于使用Spring进行数据注入
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

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

    public Account findAccountByName(String accountName) {
        List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
        if (accounts.isEmpty()) {
            return null;
        }
        if (accounts.size() > 1) {
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }

    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set name = ?, money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
    }
}

还有就是我们需要配置一下bean.xml。配置账户的持久层

<?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">

    <!--配置账户的持久层-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
   ...
</beans>

下面我们就可以来创建测试类写测试方法来测试我们的代码了,和上面一样=简单起见我们复制一下JdbcTemplateDemo2.java,复制到相同的包下命名为JdbcTemplateDemo4.java。

/**
 * JdbcTemplate 的最基本用法
 */
public class JdbcTemplateDemo4 {
    public static void main(String[] args) {
        //1.获取IOC容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.根据id获取bean对象
        IAccountDao accountDao = (IAccountDao) ac.getBean("accountDao");
        //3.执行操作
        Account account = accountDao.findAccountById(1);
        System.out.println(account);

        account.setMoney(10000f);
        //执行更新操作
        accountDao.updateAccount(account);
    }
}

我们运行一下上面的代码可以正常运行,但是如果我们还有其他的IAccountDao实现类呢?每个类都要写 private JdbcTemplate jdbcTemplate; 这样就会有重复代码。怎么办呢?往下看。

1.4 JdbcDaoSupport的使用以及Dao的两种编写方式

对于上面提到的多个Dao的实现类都要写 private JdbcTemplate jdbcTemplate; 的问题,我们可以把重复的代码提取出来放到父类中,然后各个Dao的实现类都基础自实现类。下面我们就来写一个Dao实现类的父类JdbcDaoSupport.java

/**
 * 此类用于抽取 dao 中的重复代码
 */
public class JdbcDaoSupport {
    private JdbcTemplate jdbcTemplate;

    // 用于使用Spring进行数据注入
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setDataSource(DataSource dataSource) {
        if (jdbcTemplate == null) {
            jdbcTemplate = createJdbcTemplate(dataSource);
        }
    }

    private JdbcTemplate createJdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

然后我们在复制一个AccountDaoImpl.java到相同的包下命名为AccountDaoImpl2.java
让其集成自JdbcDaoSupport

/**
 * 账户的持久层实现类
 * 这个版本适合使用xml配置,因为继承了 JdbcDaoSupport 这个是Spring提供的不方便使用注解
 */
public class AccountDaoImpl2 extends JdbcDaoSupport implements IAccountDao {
    public Account findAccountById(Integer accountId) {
    	// 使用父类中的JdbcTemplate
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
        return accounts.isEmpty() ? null : accounts.get(0);
    }

    public Account findAccountByName(String accountName) {
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
        if (accounts.isEmpty()) {
            return null;
        }
        if (accounts.size() > 1) {
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }

    public void updateAccount(Account account) {
        super.getJdbcTemplate().update("update account set name = ?, money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
    }
}

然后修改一下bean.xml

<?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">

    <!--配置账户的持久层 改成 AccountDaoImpl2-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl2">
        <!--<property name="jdbcTemplate" ref="jdbcTemplate"></property>-->
        <!--只需要注入DataSource就行了,因位会触发创建JdbcTemplate的方法-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

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

    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>

然后创建一个测试类JdbcTemplateDemo4.java

/**
 * JdbcTemplate 的最基本用法
 */
public class JdbcTemplateDemo4 {
    public static void main(String[] args) {
        //1.获取IOC容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.根据id获取bean对象
        IAccountDao accountDao = (IAccountDao) ac.getBean("accountDao");
        //3.执行操作
        Account account = accountDao.findAccountById(1);
        System.out.println(account);

        account.setMoney(30000f);
        //执行更新操作
        accountDao.updateAccount(account);
    }
}

运行一下测试类JdbcTemplateDemo4中的main方法,可以发现仍然可以正常运行。这时我们把我们写的这个JdbcDaoSupport.java删除掉,会发现AccountDaoImpl2.java中会自动导入 org.springframework.jdbc.core.support.JdbcDaoSupport 因为我们写的这个父类Spring都已经给我准备好了。我们在运行一下测试方法,可以发现仍然可以正常运行。
这两种Dao的实现方法都可以正常使用,第一种 不继承JdbcDaoSupport的Dao实现类适合使用注解配置IOC。第二种基础自JdbcDaoSupport的Dao实现类适合使用XML配置IOC。

2.Spring基于AOP的事务控制

2.1 Spring基于XML的AOP事务控制

我们新一个Module命名为day04_02account_aop_xml,我们来根据昨天的一个Module的代码来改造,我们把day03_01account这个Module里src下的所有文件都拷贝到我们新建的Module里src下。并且根据day03_01account这个Module来编写我们新建的Module里的pom.xml文件。因为我们使用Spring的AOP来实现动态代理,所以我们现在就不再需要factory这个包里的内容,所以把这个包整体都删除掉,然后我们只是来演示Spring使用AOP来进行事务控制,所以在service里AccountServiceImpl_OLD这个类也就不需要了。最后整个Module的结构如下。
在这里插入图片描述
然后我们来修改一下bean.xml文件

<?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"
       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">

    <!-- 配置Service -->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <!-- 使用set方法注入 dao对象-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置Dao对象-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!-- 使用set方法注入 QueryRunner -->
        <property name="runner" ref="runner"></property>
        <!-- 注入ConnectionUtils -->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--配置 QueryRunner 对象,因为 bean 对象默认是单例对象,所以有可能发生线程安全问题。所以让scope设置为prototype为多例-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源, 因为我们使用的是绑定在线程上的连接,所以这个不用传数据源-->
        <!--<constructor-arg name="ds" ref="dataSource"></constructor-arg>-->
    </bean>

    <!--配置数据源,使用数据库连接池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--注入链接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!-- 配置Connection的工具类 ConnectionUtils -->
    <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
        <!-- 注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置事务管理器-->
    <bean id="txManager" class="com.itheima.utils.TransactionManager">
        <!-- 注入ConnectionUtils -->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--配置aop-->
    <aop:config>
        <!--配置通用切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <aop:aspect id="txAdvice" ref="txManager">
            <!--配置前置通知:开启事务-->
            <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
            <!--配置后置通知:提交事务-->
            <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
            <!--配置异常通知:回滚事务-->
            <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
            <!--配置最终通知:释放连接-->
            <aop:after method="release" pointcut-ref="pt1"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

然后我们恢复一下数据库表里的数据
在这里插入图片描述
然后我们把AccountServiceImpl.java这个类的转账方法transfer中的 1/0取消注释

public class AccountServiceImpl implements IAccountService {
	...
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
        //2.1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.2.根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //2.3.转出账户的存款减去转账金额
        source.setMoney(source.getMoney() - money);
        //2.4.转入账户的存款加转账金额
        target.setMoney(target.getMoney() + money);
        //2.5.更新转出账户
        accountDao.updateAccount(source);

        int i = 1 / 0;

        //2.6.更新转入账户
        accountDao.updateAccount(target);
    }
}

最后修改一下我们的测试类AccountServiceTest中的测试方法

/**
 * 使用Junit单元测试,测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    IAccountService as;

    @Test
    public void testTransfer() {
        as.transfer("bbb", "aaa", 100f);
    }
}

然后执行这个测试方法testTransfer,控制台输入如下。
在这里插入图片描述
我们在看一下数据库里表的情况如下,可以看出数据没有发生变化,说明事务控制成功了。
在这里插入图片描述

2.2 Spring基于注解的AOP事务控制

我们在新建一个Module命名为day04_03account_aoptx_anno,然后把Module名为day04_02account_aoptx_xml里src下的文件拷贝到我们新建的Module里src下,并修改pom.xml文件。首先要配置一下IOC的注解,所以要在xml配置要扫描的包,所以要导入对应的依赖。

<?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:context="http://www.springframework.org/schema/context"
       xmlns:sop="http://www.springframework.org/schema/aop"
       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/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置Spring创建IOC容器时要扫描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>
	...
</beans>

然后配置service,给service加上注解修改一下AccountServiceImpl.java在bean.xml中也就不在需要service的配置了。

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired // 因为只有一个dao所以可以直接使用Autowired进行自动类型注入,所以也不在需要set方法了
    private IAccountDao accountDao;
    ...
}

下面配置一下dao,修改一下AccountDaoImpl.java在bean.xml中也就不在需要dao的配置了。

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private QueryRunner runner;
    @Autowired
    private ConnectionUtils connectionUtils;
    ...
}

下面修改一下ConnectionUtils.java,给该类加上注解,在bean.xml对ConnectionUtils的ioc配置就不需要了

@Component("connectionUtils")
public class ConnectionUtils {
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    @Autowired//因为这个Module中只有一个DataSource对象,所以可以使用Autowired进行自动类型注入
    private DataSource dataSource;
    ...
}

下面就来配置一下事务管理的类TransactionManager.java,配置好后就可以把bea.xml中aop的配置删除掉了

@Component("txManager")
@Aspect // 表明当前类是一个切面
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;

    /**
     * 这个方法用于表示切入点表达式
     */
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1() {
    }

    /**
     * 开始事务
     */
    @Before("pt1()") // 引用切入点表达式时必须要写pt1后面的()
    public void beginTransaction() {
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    @AfterReturning("pt1()")
    public void commit() {
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    @AfterThrowing("pt1()")
    public void rollback() {
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放连接
     */
    @After("pt1()")
    public void release() {
        try {
            connectionUtils.getThreadConnection().close(); // 把连接返回连接池中
            connectionUtils.removeConnection();//把连接和线程进行解绑
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

最后在bean.xml中国配置开启注解的aop。最终的bean.xml文件如下,

<?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:context="http://www.springframework.org/schema/context"
       xmlns:sop="http://www.springframework.org/schema/aop"
       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/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置Spring创建IOC容器时要扫描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <!--配置 QueryRunner 对象,因为 bean 对象默认是单例对象,所以有可能发生线程安全问题。所以让scope设置为prototype为多例-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源, 因为我们使用的是绑定在线程上的连接,所以这个不用传数据源-->
        <!--<constructor-arg name="ds" ref="dataSource"></constructor-arg>-->
    </bean>

    <!--配置数据源,使用数据库连接池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--注入链接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!--开启Spring对注解AOP的支持-->
    <sop:aspectj-autoproxy></sop:aspectj-autoproxy>
</beans>

现在我们把数据库中的表数据恢复成如下:
在这里插入图片描述
首先我们把AccountServiceImpl.java中的transfer方法里的1/0注释掉下面我们来运行下测试类AccountServiceTest中的测试方法testTransfer,控制台输出如下,通过查看数据库表的数据发现数据没有变化,但是为啥报错了呢,通过查询资料发现这个是Spring的一个 bug,据说在高版本中已经解决了,这个bug就是在使用注解配置Spring的aop时调用顺序是有问题的,它会先调用最终通知后调用后置通知。
在这里插入图片描述
这个问题怎么解决呢?我们可以使用环绕通知来解决,我们自己写调用的代码,自己来控制调用的顺序。下面就来改造一下TransactionManager.java,因为要我们自己写代码来实现环绕通知,所以要前置通知、后置通知、异常通知、最终通知的注解注释掉或者删除掉。创建一个新的方法 aroundAdvice 并加上环绕通知的注解 Around 代码如下。

@Component("txManager")
@Aspect // 表明当前类是一个切面
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;

    /**
     * 这个方法用于表示切入点表达式
     */
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1() {
    }

    /**
     * 开始事务
     */
    //@Before("pt1()") // 引用切入点表达式时必须要写pt1后面的()
    public void beginTransaction() {
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    //@AfterReturning("pt1()")
    public void commit() {
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    //@AfterThrowing("pt1()")
    public void rollback() {
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放连接
     */
    //@After("pt1()")
    public void release() {
        try {
            connectionUtils.getThreadConnection().close(); // 把连接返回连接池中
            connectionUtils.removeConnection();//把连接和线程进行解绑
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Around("pt1()") // 环绕通知
    public Object aroundAdvice(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        try {
            //1.获取参数
            Object[] args = pjp.getArgs();
            //2.开启事务
            this.beginTransaction();
            //3.执行方法
            rtValue = pjp.proceed(args);
            //4.提交事务
            this.commit();

            return rtValue;

        } catch (Throwable e) { // Exception 不能捕获到这个异常
            //5.回滚
            this.rollback();
            throw new RuntimeException(e);
        } finally {
            //6.释放事务
            this.release();
        }
    }
}

然后我们在来运行一下测试类AccountServiceTest中的测试方法testTransfer,通过观察控制台发现程序正常执行,并且数据库表的数据也都正常更改。然后我们把AccountServiceImpl.java里的方法transfer里的1/0打开注释,在一次运行测试方法,发现程序报错如下,但是通过查看数据库表里的数据发现现在可以正常的进行事务控制了。
在这里插入图片描述

3.Spring中的事务控制

3.1 Spring 中事务控制的 API 介绍

3.1.1 PlatformTransactionManager 接口

此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法,如下图:
在这里插入图片描述
我们在开发中都是使用它的实现类,如下图:
在这里插入图片描述

3.1.2 TransactionDefinition 接口

它是事务的定义信息对象,里面有如下方法:
在这里插入图片描述

3.1.2.1 事务的隔离级别

在这里插入图片描述

3.1.2.2 事务的传播行为

在这里插入图片描述

3.1.2.3 超时时间

默认值是-1,没有超时限制。如果有,以秒为单位进行设置。

3.1.2.4 是否是只读事务

建议查询时设置为只读。

3.1.3 TransactionStatus 接口

此接口提供的是事务具体的运行状态,方法介绍如下图
在这里插入图片描述

3.2 Spring中事务控制的代码准备

我们先来新建一个Module命名为dayday04_04tx。我们现在创建的这个Module是为后面的代码演示做准备

  • 首先我们需要修改pom.xml导入我们需要的坐标
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.itheima</groupId>
    <artifactId>day04_04tx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency><!-- 支持切入点表达式 -->
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency><!-- spring 的测试支持 -->
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
  • 编写实体类Account.java
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float 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;
    }
    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
  • 添加IAccountdao接口和其实现类AccountDaoImpl.java
/**
 * 账户的持久层接口
 */
public interface IAccountDao {
    /**
     * 根据id查询账户
     */
    Account findAccountById(Integer accountId);
    /**
     * 根据名称查询账户
     */
    Account findAccountByName(String accountName);
    /**
     * 更新账户信息
     */
    void updateAccount(Account account);
}
/**
 * 账户的持久层实现类
 * 这个版本适合使用xml配置,因为继承了 JdbcDaoSupport Spring IOC不方便提供注解注入数据,但是适合使用XML进行配置
 */
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
    public Account findAccountById(Integer accountId) {
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
        return accounts.isEmpty() ? null : accounts.get(0);
    }
    public Account findAccountByName(String accountName) {
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
        if (accounts.isEmpty()) {
            return null;
        }
        if (accounts.size() > 1) {
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }
    public void updateAccount(Account account) {
        super.getJdbcTemplate().update("update account set name = ?, money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
    }
}
  • 编写IAccountService接口和其实现类AccountServiceImpl.java
/**
 * 账户的业务层接口
 */
public interface IAccountService {
    /**
     * 根据id查询账户信息
     */
    Account findAccountBuId(Integer id);
    /**
     * 转账
     * @param sourceName 转出账户名称
     * @param targetName 转入账户名称
     * @param money      转账金额
     */
    void transfer(String sourceName, String targetName, float money);
}
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;
    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }
    public Account findAccountBuId(Integer id) {
        return accountDao.findAccountById(id);
    }
    public void transfer(String sourceName, String targetName, float money) {
        System.out.println("transfer....");
        //根据账户名查询转出账户
        Account sourceAccount = accountDao.findAccountByName(sourceName);
        //根据账户名查询转入账户
        Account targetAccount = accountDao.findAccountByName(targetName);
        //转出账户减少money
        sourceAccount.setMoney(sourceAccount.getMoney() - money);
        //转入账户增加money
        targetAccount.setMoney(targetAccount.getMoney() + money);

        //更新账户
        accountDao.updateAccount(sourceAccount);

        int i = 1 / 0;

        accountDao.updateAccount(targetAccount);
    }
}
  • 然后我们还需要编写spring配置文件bean.xml
<?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">
    <!--配置业务层-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <!--配置账户的持久层-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!--只需要注入DataSource就行了,因位会触发创建JdbcTemplate的方法-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>
  • 最后们在编写一个测试类AccountServiceTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
    @Autowired
    private IAccountService as;
    @Test
    public void testTransfer() {
        as.transfer("aaa", "bbb", 100f);
    }
}

最后我们整个的准备项目结构如下
在这里插入图片描述

3.3 Spring 基于 XML 的声明式事务控制(配置方式)

首先我们新建一个Module命名为day04_05_tx_xml,然后我们把我们上面创建一个名为day04_04tx的准备项目的src里的内容拷贝到我们新建的Module的src下。不要忘记填写pom.xml文件哟,可以根据day04_04tx的pom.xml文件进行填写。
在这个Module里我们不需要修改太多的内容,我们主要修改spring的配置文件bean.xml。
因为我们要进行事务的控制所以在修改bean.xml文件时首先要导入事务控制的约束和aop的约束。我们可以在spring的官方文档中找到,我们这里使用的spring的版本为5.0.2.RELEASE,
在这里插入图片描述
然后点击上图中红框中的连接Data Access。然后在这个页面搜索 xmlns:tx ,找到如下内容,替换掉bean.xml文件里原来的约束。我们会发现这个约束里已经包含了aop的约束。
在这里插入图片描述

  • spring中基于XML的声明式事务控制配置步骤
    在这里插入图片描述
  • 最后完整的bean.xml如下
<?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">
    <!--配置业务层-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <!--配置账户的持久层-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!--只需要注入DataSource就行了,因位会触发创建JdbcTemplate的方法-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    <!--spring中基于XML的声明式事务控制配置步骤
        1、配置事务管理器
        2、配置事务的通知
            此时我们需要导入事务的约束 tx名称空间,同时也需要aop的约束
            使用tx:advice 标签配置事务通知
                属性:
                    id:个事务通知起一个唯一的标识
                    transaction——manager:给事务通知提供一个事务管理器引用
         3、配置AOP中的通用切入点表达式
         4、建立事务通知和切入点表达式的对应关系
         5、事务的属性
            实在事务的通知tx:advice标签的内部配置
    -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务的通知-->
    <tx:advice id="txAvice" transaction-manager="transactionManager">
        <!--配置事务的属性
            isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认隔离级别。
            propagation:用于指定事务的传播行为,默认值是 REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择 SUPPORTS
            read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
            timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。

            no-rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚
            rollback-for:用于指定一个异常,当产生该异常时,事务不回滚。产生其他异常时事务回滚。没有默认值,表示任何异常都回滚
        -->
        <tx:attributes>
            <!--transfer 表示一个方法-->
            <tx:method name="transfer" propagation="REQUIRED" read-only="false"/>
            <!--表示所有的方法-->
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <!--find* 表示方法名以find开头的方法,例如查询方法-->
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <!--配置AOP-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        <!--建立切入点表达式和事务通知的对应关系-->
        <aop:advisor advice-ref="txAvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>
</beans>

下面我运行一下测试类中的测试方法,就会发现事务控制就可以啦。

3.4 Spring 基于 注解 的声明式事务控制(配置方式)

首先创建一个新的Module命名为day04_06tx_anno,然后把上一个名为day04_05_tx_xml的Module的src下的文件拷贝到我们新建的Module的src下,记得填写pom.xml文件可以根据上一个Module进行填写。
首先我们把spring IOC配置改成注解的形式。
-我们先把dao接口的实现类AccountDaoImpl.java改成注解的形式

/**
 * 账户的持久层实现类
 * 因为使用注解进行开发,所以不能再继承 JdbcDaoSupport 这个类啦,
 */
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public Account findAccountById(Integer accountId) {
        List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
        return accounts.isEmpty() ? null : accounts.get(0);
    }
    public Account findAccountByName(String accountName) {
        List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
        if (accounts.isEmpty()) {
            return null;
        }
        if (accounts.size() > 1) {
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }
    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set name = ?, money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
    }
}
  • 然后修改一下bean.xml,因为要配置spring IOC要扫描的包所以要添加 xmlns:context 的约束。
<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:scantext="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置spring IOC要扫描的包-->
    <scantext:component-scan base-package="com.itheima"></scantext:component-scan>
    <!--配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--只需要注入DataSource就行了,因位会触发创建JdbcTemplate的方法-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    <!--spring中基于注解的声明式事务控制配置步骤
        1、配置事务管理器
        2、开始spring对注解事务的支持
        3、在需要事务支持的地方使用 @Transcational注解
    -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--开启spring对注解事务的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
  • 最后修改service的实现类AccountServiceImpl.java配置ico同时添加事务控制的注解
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true) // 参数和使用xml配置时一样
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

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

    //因为这里的事务读写都需要,所以需要单独配置
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void transfer(String sourceName, String targetName, float money) {
        System.out.println("transfer....");
        //2.1根据账户名查询转出账户
        Account sourceAccount = accountDao.findAccountByName(sourceName);
        //2.2根据账户名查询转入账户
        Account targetAccount = accountDao.findAccountByName(targetName);
        //2.3转出账户减少money
        sourceAccount.setMoney(sourceAccount.getMoney() - money);
        //2.3转入账户增加money
        targetAccount.setMoney(targetAccount.getMoney() + money);

        //2.4更新账户
        accountDao.updateAccount(sourceAccount);

        int i = 1 / 0;

        accountDao.updateAccount(targetAccount);
    }
}

至此Spring 基于 注解 的声明式事务控制配置就完成了。下面我们可以用个运行测试方法来查看是否成功配置了事务控制。
我们在开发的时候建议使用xml进行配置spring的事务控制,因为如果一个service中有多个方法他们要求的数据库隔离级别不同,就需要写很多个Transactional注解,而使用xml进行配置相对来说比较简单。

3.4 Spring 基于 纯注解 的声明式事务控制

我们先来创建一个新Module命名day04_07anno_tx_withoutxml。首先我们可以根据上一个名为day04_06tx_anno的Module的pom.xml来修改我们新建Module的pom.xml文件。然后把day04_06tx_anno里src下的文件拷贝到我们新建的Module的src下。下面我们一步一步的来去除bean.xml里的内容。新建一个包com.config,用于存放我们的配置类。

  • 首先为了除去bean.xml文件中的jdbcTemplate对象和dataSource对象,我们在config包下,新建一个JdbcConfig.java
/**
 * 连接数据库相关的配置类
 */
public class JdbcConfig {
    @Value("${jdbc.driver}") // 使用spring的er表达式
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    /**
     * 创建 JdbcTemplate 对象
     * Qualifier注解 在给方法参数修饰时可以单独使用,参数 value 表示bean的id
     */
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    /**
     * 创建一个数据源对象
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}
  • 为了除去bea.xml中的事务控制有关的配置,我们在config包里新建一个TransactionConfig.java
/**
 * 和事务相关的配置类
 */
public class TransactionConfig {
    /**
     * 用于创建事务管理器对象
     * PlatformTransactionManager:事务管理器接口,DataSourceTransactionManager为其实现类
     */
    @Bean(name = "transactionManager")
    public PlatformTransactionManager createTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
        DataSourceTransactionManager dstm = new DataSourceTransactionManager(dataSource);
        return dstm;
    }
}
  • 最后为了我们的Module可以删除整个bean.xml,我们在config包下新建一个SpringConfiguration.java相当于xml配置时的bean.xml
/**
 * spring 的配置类相当于 bean.xml
 */
@Configuration // 如果这个类以字节码的形式传给 AnnotationContext 对象,那么这个注解就可以不用写
@ComponentScan("com.itheima") //用于配置要扫描的包
@Import({JdbcConfig.class, TransactionConfig.class}) // 导入其他的配置类
@PropertySource("jdbcConfig.properties") // 加载jdbc的配置文件
@EnableTransactionManagement // 实现 开启spring对注解事务的支持
public class SpringConfiguration {
}
  • 最后我们还需要修改一下我们的测试类AccountServiceTest.java
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = "classpath:bean.xml") // 用于使用xml配置
@ContextConfiguration(classes = SpringConfiguration.class) //用于使用注解配置
public class AccountServiceTest {
    @Autowired
    private IAccountService as;

    @Test
    public void testTransfer() {
        as.transfer("aaa", "bbb", 100f);
    }
}

至此整个纯注解配置spring事务管理的Module编写完成。
在这个Module中我们基本回顾了前三题天所学的所有spring注解。

3.5 Spring 基于 编程式 的事务控制

我们在来新建一个Module命名为day04_08account_tx,然后根据 day04_04tx 这个Module填写pom.xml文件。然后把day04_04tx的src下的文件拷贝到我们新建的Module的src下。
基于编程式的事务控制需要使用一个 org.springframework.transaction.support.TransactionTemplate 类,这个类中有一个 execute 方法。
在这里插入图片描述
为什么这个方法里没有事务开启的操作呢,因为这个类还需要一个注入一个 PlatformTransactionManager 类型的参数,在spring的ioc配置中我配置为DataSourceTransactionManager这个对象。在执行这个execute方法之前会先执行 PlatformTransactionManager 的 doBegin 方法来开启事务。
因为仍然是事务控制,所以需要在bean.xml文件中配置一个事务管理器。具体的bean.xml配置如下。

<?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">
    <!--配置业务层-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="transactionTemplate" ref="transactionTemplate"></property>
    </bean>
    <!--配置账户的持久层-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!--只需要注入DataSource就行了,因位会触发创建JdbcTemplate的方法-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--事务模板对象-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <!--事务模板对象需要注入一个 配置事务管理器 对象-->
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>
</beans>

最后我们在类配置一下AccountServiceImpl.java,把需要事务控制的方法进行改造。

public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;
    
    // 用于事务控制
    private TransactionTemplate transactionTemplate;
    
    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }
    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }
    public Account findAccountBuId(final Integer id) {
        return transactionTemplate.execute(new TransactionCallback<Account>() {
            public Account doInTransaction(TransactionStatus transactionStatus) {
                return accountDao.findAccountById(id);
            }
        });
    }

    public void transfer(final String sourceName, final String targetName, final float money) {
	    // 执行transactionTemplate.execute方法,创建一个匿名内部类TransactionCallback来作为参数。
        transactionTemplate.execute(new TransactionCallback<Object>() {
            public Object doInTransaction(TransactionStatus transactionStatus) {
                System.out.println("transfer....");
                //2.1根据账户名查询转出账户
                Account sourceAccount = accountDao.findAccountByName(sourceName);
                //2.2根据账户名查询转入账户
                Account targetAccount = accountDao.findAccountByName(targetName);
                //2.3转出账户减少money
                sourceAccount.setMoney(sourceAccount.getMoney() - money);
                //2.3转入账户增加money
                targetAccount.setMoney(targetAccount.getMoney() + money);

                //2.4更新账户
                accountDao.updateAccount(sourceAccount);

                // int i = 1 / 0;

                accountDao.updateAccount(targetAccount);
                return null;
            }
        });
    }
}

具体执行流程如下
在这里插入图片描述
这就是整个Spring 基于 编程式 的事务控制。但是如果Service的实现类中有很多方法都需要进行事务控制就需要写很多这种代码,又会造成service的实现类比较臃肿,有很多重复代码,所以在实际开发中很少使用。

南京银行信息技术岗提前批笔试与面试 1、笔试 笔试大致可以分为三部分:性格测试、行测题目、专业知识 性格测试 性格测试一般会有较多的体量,可能一共大概有一百多道接近两百道的样子,这个是不限时的。 前面有100道是这样的题目,就是有四个选项都是各种形容词,然后分别选出最符合自己的与最不符合自己的选项。这种题你就顺着自己的潜意识填就行了,但是前后要一致,我怀疑他出这么多题,可能就是考一考你记不记得前面填了啥。比如你做到第90题的时候,可能会出现和前面第2题类似的四个形容词,更有甚者就是一模一样的,你要对自己之前选的什么有个比较清楚的记忆。 阅读详情

相关推荐

三. zemax光学设计流程+案例

文章目录zemax 基本操作:1. 建立光学基本模型2. 像质评价3. 优化(重点)4. 公差分析简单镜头设计 zemax 基本操作: 1. 建立光学基本模型 2. 像质评价 3. 优化(重点) 对系统设定的约束条件或目标值 统称为操作数。 操作数: 光学特性参数(焦距EFFL,近轴放大率PMAG,入瞳位置EMPP) 像差参数(球差SPHA,彗差COMA,像散ASTI) 边界条件(中心厚度值CTVA,边缘厚度值ETAV) 优化的主要步骤: 设置评价函数和优化操作数 一般小像差系统采用波像差 构建

Taiiiii的博客 8050

IDEA 出现Module “**“ must not contain source root “**“. The root already belongs to module ”**“问题解决

具体错误: Module "XudongMaster_V1_0" must not contain source root "D:\idea 2015\XudongMaster_V1_0\CommonService\src\main\java". The root already belongs to module "CommonService" 解决办法: 以上报错的意思是XudongMa...

旭东怪的博客 2万+

【时间序列预测算法】——Holt-Winters算法介绍及代码实现

背景 一阶二阶三阶指数平滑方法拟合效果均较好,通过结合数据自然属性,数据自身、趋势变化、季节性,提出了这三个角度的衡量量,可以说,理解起来更自然、更亲切。 公式 一阶: sts_tst​是t时刻对xtx_txt​的估计值,这里只有一个参数α,是数据本身的平滑属性的体现。 二阶: 引入了时间趋势变量btb_tbt​,优化了一下一阶的公式。同时还可以预测t+m时刻的值: 三阶: 又引入了季节性周期描述变量ctc_tct​,优化了一下二阶的公式。同时还可以预测t+m时刻的值: NOTE:ct

BlackValley的博客 1万+

Spring中的jdbcTemplate以及事务控制

JdbcTemplate 概述 它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。spring 框架为我们提供了很多的操作模板类。 操作关系型数据的: JdbcTemplate HibernateTemplate 操作 nosql 数据库的: RedisTemplate 操作消息队列的: JmsTemplate jdbcTemplate需要导的架包: spring-jdbc-5.0.2.RELEASE.jar

Apikaqiu的博客 957

ZynqMP双核通信避坑指南:Linux驱动+裸机固件协同开发实录

本文详细解析了Xilinx ZynqMP芯片中APU与RPU双核通信的技术实现,重点介绍了通过IPI中断实现Linux驱动与裸机固件协同开发的实战经验。内容涵盖硬件寄存器配置、Linux驱动开发、RPU裸机程序编写以及双核调试技巧,为开发者提供了一套完整的工程化解决方案,帮助规避常见开发陷阱。

weixin_29168153的博客 126

Label Studio保姆级教程:从零搭建医疗影像标注平台(DICOM格式支持)

本文提供基于Label Studio从零搭建医疗影像标注平台的保姆级教程。针对DICOM格式支持,详细阐述了环境部署、DICOM深度集成与预处理、团队协作权限管理以及质量审核闭环等核心环节,旨在构建一个高效、可落地的医疗AI数据标注解决方案,助力模型落地。

nft7creator的博客 302

Spring学习第四天(Spring中的JdbcTemplate的使用、Spring中的事务控制)

一、Spring中JdbcTemplate的使用 1.当不继承JdbcDaoSupport时,需要定义JdbcTemplate对象,并在xml文件中配置JdbcTemplate和注入DataSource对象 1.JdbcDaoSupport的使用:让持久层继承JdbcDaoSupport类就能不用定义JdbcTemplate对象,免去了注入JdbcTemplate的步骤 **注意:**免去定义JdbcTemplate对象后,需要使用父类的getJdbcTemplate()方法来调用JdbcTemplate的

大脑不能为空 273

RTC温度补偿:如何让时间分秒不差?

RTC(实时时钟)温度补偿技术通过"检测温度→计算误差→动态调整频率"的闭环机制,解决振荡器频率随温度变化导致的时间误差问题。核心步骤包括:1)高精度温度检测;2)基于温度-频率误差模型计算偏差;3)通过硬件(可编程电容/DAC)或软件方式调整振荡频率。该技术需突破温度检测精度、误差模型拟合、频率调整分辨率等关键点,根据不同应用场景(消费电子±5ppm至军工±0.1ppm)选择匹配方案。典型实现如STM32的分段线性补偿算法,可将工业级RTC精度提升至±2ppm以内。

上市公司前技术负责人,20 年软硬件深耕 + 百人研发团队管理经验,名校理工科背景加持。从技术攻坚到团队掌舵,沉淀海量项目实战与管理心得,专注分享软硬件干货、疑难问题解决方案,既是靠谱知识传播者,也盼链接优质项目共成长。 1228

spring(六)JdbcTemplate,AOP事务控制(简单实现)

JdbcTemplate,AOP事务控制(简单实现)1. JdbcTemplate的使用及Dao的两种编写方式1.1 注解式1.2 继承JdbcDaoSupport2. AOP事务控制2.1 基于XML的AOP实现事务控制2.2 基于注解的AOP实现事务控制 1. JdbcTemplate的使用及Dao的两种编写方式 1.1 注解式 <context:component-scan base-package="com.java"></context:component-scan>

weixin_44962371的博客 457

12、信号采样与Z变换的全面解析

本文全面解析了信号采样和Z变换的核心概念与应用。内容涵盖采样基础、采样频率与奈奎斯特率的关系、离散时间处理模拟信号的方法、采样率转换技术,以及Z变换的定义、性质、常见变换对和逆变换计算。通过实例分析和流程图展示,帮助读者深入理解数字信号处理的关键步骤,为相关领域的学习与应用打下坚实基础。

Jerry的专栏 118

第9.3章 从CMake基础到自动驾驶实战:构建跨平台C/C++项目的全能指南

《CMake构建实战:项目开发卷》精要指南 本文系统梳理了许宏旭《CMake构建实战》核心内容,涵盖C++构建全流程: 基础构建:详解MSVC/GCC编译器使用、Makefile编写及静态/动态库构建方法 CMake核心:解析语法结构、变量控制、文件操作等关键命令,阐述项目全生命周期管理 实战应用:通过快速排序、手写数字识别项目演示现代CMake实践,特别针对自动驾驶领域给出多传感器融合、SLAM等场景的CMake配置方案 特色价值:本书填补了官方文档实例不足的空白,提供300+代码示例,是掌握工业级C++

行知SLAM 1343

spring中的JdbcTemplate、基于AOP的事务控制、事务控制

1、spring中的JdbcTemplate 概述: 它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。spring 框架为我们提供了很多的操作模板类。 1、导jar包 spring-jdbc-5.0.2.RELEASE.jar 中,我们在导包的时候,除了要导入这个 jar 包 外,还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。 <?xml version="1.0" encoding="UTF-8"?> &l

qq_43430343的博客 405

核磁共振常用的文件格式介绍:NIfTI volume 格式 (*.nii), GIFTI 格式 (*.gii), CIFTI 格式 (*.nii)

NIfTI volume 格式 (*.nii)GIFTI 格式 (*.gii)CIFTI 格式 (*.nii)Scene file文件 (*.scene)规格文件 (*.spec)解剖 (*.annot)一个场景文件包含一个或多个场景;每个场景实际上是保存时 wb_view 内部状态的快照。这包括有关加载哪些文件、如何显示、如何配置选项卡的信息(选择了哪些表面、在每个层中选择了哪些文件、打开了哪些层、哪些视图旋转、调色板设置、平铺选项卡等),以及在它们上面添加了哪些注释(文本、箭头、圆圈等)。

从事脑科学核磁共振方法学研究,在Nature communications等权威期刊发表研究论文,熟练掌握磁共振处理方法和统计学方法,欢迎大家和我交流。 3042

spring学习-JdbcTemplate简单操作和事务控制

JdbcTemplate JdbcTemplate是spring对JDBC操作的进一步封装,下面就通过简单的CRUD来了解JdbcTemplate如何操作,连接池直接用的spring内置的连接池DriverManagerDataSource 首先配置JdbcTemplate和数据源 <bean id="jdbcTemplate" class="org.springframework....

林北 6703

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Spring(四)JdbcTemplate及Spring事务控制

这里写目录标题一级目录二级目录三级目录 一级目录 二级目录 三级目录

weixin_45077780的博客 544

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

第四天spring的jdbcTemplate和spring的事务控制

一、JdbcTemplate基本使用 ①导入spring-jdbc和spring-tx坐标 ②创建数据库表和实体 ③创建JdbcTemplate对象 spring的配置文件修改为: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" ...

qq_38288937的博客 472

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Spring的学习笔记(黑马)

1. spring概述1.1 Spring是什么(理解)1.2 Spring发展历程 (了解)1.3 Spring的优势(理解)1.4 Spring的体系结构(了解)2. spring快速入门2.1 Spring程序开发步骤2.2 导入Spring开发的基本包坐标2.3 编写Dao接口和实现类2.4 创建Spring核心配置文件2.5 在Spring配置文件中配置UserDaoImpl2.6 使用Spring的API获得Bean实例3. Spring配置文件3.1 Bean标签基本配置3.2 Bean标签.

weixin_44226752的博客 4132

Sping学习四——JdbcTemplate与事务控制

目录 一、Spring中的JdbcTemplate 二、作业分析 1)基于XML的AOP配置 2)基于注解的AOP实现事务控制 三、Spring的声明式事务控制 1、spring中事务控制的一组API 2、spring中基于xml的声明式事务控制----配置步骤 3.spring中基于注解的声明式事务控制 day4..... 一、Spring中的JdbcTemplate ...

qq_36964872的博客 606

五、Spring中的JdbcTemplate和事务控制

1 Spring中的JdbcTemplate 1.1 JdbcTemplate概述 它是Spring框架中提供的一个对象,是对原始Jdbc API对象的简单封装。Spring框架为我们提供了很多的操作模板类。 操作关系型数据的:JdbcTemplate、HibernateTemplate; 操作nosql数据库的:RedisTemplate; 操作消息队列的:JmsTemplate。 我们今天的主角在 spring-jdbc-5.0.2.RELEASE.jar 中,我们在导包的时候,除了要导入这个jar

wssh1854的博客 379

JdbcTemplate的简单使用(含在Spring中常规使用)+spring 中的aop自动事务控制

最近在学spring,再学一遍,回顾回顾还有什么遗漏的地方。技术啊技术,永远地学不完。学技术不如学数学,学数学不如背算法,背算法不如学管理,学管理不如考公务员。。。——特么学什么都难 一般使用 先在MySQL数据库中创建一个Account表,表结构如下代码所示: -- auto-generated definition create table account ( id int auto_increment primary key, name varchar(25)

良木 307

Spring_4 - JdbcTemplate,事务控制

文章目录Spring_4 JdbcTemplate,事务控制一,Spring中的JdbcTemplate1,JdbcTemplate概述2,JdbcTemplate对象的创造3,JdbcTemplate的增删改查操作(1)引入坐标(2)在spring配置文件bean.xml中配置JdbcTemplate(3)最基本的CRUD使用(4)查询所有操作(5)查询一个(6)查询返回一行一列数据4,在dao中使用JdbcTemplate(1)实体类(2)IAccountDao接口(3)第一种方法:实现类中定义Jdbc

reallllbj的博客 343

spring框架:简述JdbcTemplate及事务控制

本人小白一枚,欢迎大家一起讨论学习,如有错误,还望大家指教。 简述JdbcTemplate JdbcTemplate概述 它是spring框架中提供的一个对象,是对原始JDBC API对象的简单封装。spring框架为我们提供了很多操作模板类。操作关系型数据库的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate以及操作消息队列的Jms...

qq_43138206的博客 1489

spring aop增强事务 jdbcTemplate工具类

一、学习目标 理解ThreadLocal工具类使用 使用spring的aop增强事务 了解jdbcTemplate工具类 二、spring的aop完成事务控制 2.0转账逻辑环境搭建 2.0.1 创建项目导入maven依赖 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> &

weixin_43525993的博客 332

Spring---学习第四天(jdbcTemplate+Spring的事务控制)

JdbcTemplate JdbcTemplate是 spring 框架中提供的一个对象,它的作用和QueryRunning一样,是对原始 Jdbc API 对象的简单封装。 spring 框架提供了很多正对不同数据库类型的模板类。 操作关系型数据: JdbcTemplate HibernateTemplate 操作 nosql 数据库 RedisTemplate 操作消息队列 JmsTemplate 使用JdbcTemplate的两种方式 方法一  在dao中定义JdbcTemplate X

空圆小生的博客 285

Spring 框架学习第六节:Spring 中的 JdbcTemplate 与事务控制

此博客用于个人学习,来源于ssm框架的书籍,对知识点进行一个整理。 6.1 Spring 中的 JdbcTemplate: 可能有人会疑惑:Spring 中既然有 JdbcTemplate,为什么还需要与 MyBatis 框架一起使用? 两者可以在不同的场景下使用,spring JDBC的性能更好,更适合管理域的sql语句,而mybatis则可以将sql语句批量的管理起来,还可以达到sql的重用,同时mybatis还帮你做了数据库的驱动注册加载,数据库连接的创建与销毁等数据库的管理可以让你更关注于业务,更适

paynmind的博客 841

Spring(六)JdbcTemplate,事务控制

JdbcTemplate,spring事务,Transaction

weixin_50204197的博客 2959
上一篇: Java框架Spring学习教程从入门到精通
下一篇: HashMap内存泄漏
Code_Boy_Code
博客等级 码龄9年 650粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值