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

最近在学spring,再学一遍,回顾回顾还有什么遗漏的地方。技术啊技术,永远地学不完。学技术不如学数学,学数学不如背算法,背算法不如学管理,学管理不如考公务员。。。——特么学什么都难


一般使用

先在MySQL数据库中创建一个Account表,表结构如下代码所示:

-- auto-generated definition
create table account
(
    id    int auto_increment
        primary key,
    name  varchar(25)     not null,
    money float default 0 not null,
    constraint name
        unique (name)
);

再新建一个空的maven工程,在工程中创建构造如下目录结构:
在这里插入图片描述
先给出maven项目的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>org.example</groupId>
    <artifactId>20201122</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.compiler.source>11</maven.compiler.source>
        <spring.context.version>5.2.8.RELEASE</spring.context.version>
        <spring.jdbc.version>5.2.8.RELEASE</spring.jdbc.version>
        <spring.tx.version>5.2.8.RELEASE</spring.tx.version>
        <junit.version>4.13</junit.version>
        <mysql.version>8.0.20</mysql.version>
        <aspectj.version>1.9.6</aspectj.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.context.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.jdbc.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.tx.version}</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
    </dependencies>
</project>

Account.class文件内容

package com.domain;

import java.io.Serializable;

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.java

package com.dao;

import com.domain.Account;

import java.util.List;

public interface IAccountDao {
    Account findByName(String name);
    Account findById(Integer id);
    List<Account> findAll();
    void deleteAccountById(Integer id);
    void deleteAccountByName(Integer id);
    void deleteAccount(Account account);
    Integer updateAccount(Account account);
    Integer addAccount(Account account);
}

AccountDao.java

package com.dao.impl;

import com.dao.IAccountDao;
import com.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class AccountDao implements IAccountDao {
    private JdbcTemplate jdbcTemplate = null;

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

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

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

    @Override
    public Account findById(Integer id) {
        return null;
    }

    @Override
    public List<Account> findAll() {
        return null;
    }

    @Override
    public void deleteAccountById(Integer id) {

    }

    @Override
    public void deleteAccountByName(Integer id) {

    }

    @Override
    public void deleteAccount(Account account) {

    }

    @Override
    public Integer updateAccount(Account account) {
        return null;
    }

    @Override
    public Integer addAccount(Account account) {
        return null;
    }
}

我只实现了一个方法,主要是为了演示一下JdbcTemplate的一般使用,写一个demo如下所示:
JdbcTemplateDemo.java

package com.jdbcTemplateDemo;

import com.dao.impl.AccountDao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

public class JdbcTemplateDemo {
    public static void main(String[] args) {
        var jdbcTemplate = new JdbcTemplate();
        var datasource = new DriverManagerDataSource();
        datasource.setUrl("jdbc:mysql://localhost:3306/mydb2?serverTimezone=UTC");
        datasource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        datasource.setPassword("root");
        datasource.setUsername("root");
        jdbcTemplate.setDataSource(datasource);
        var accountDao = new AccountDao();
        accountDao.setJdbcTemplate(jdbcTemplate);
        var account = accountDao.findByName("aaa");
        System.out.println(account.toString());
    }
}

再给数据库account插入几条数据,如下所示:
在这里插入图片描述
运行如上demo,结果如下所示:
在这里插入图片描述
运行结果出来了,这是jdbctemplate的一般使用。


在spring中使用

一般情况下,jdbctemplate在实际开发中都是没人用的存在,现在基本都是用mybatis的,但是为什么还要把这个jdbctemplate写出来呢?因为jdbctemplate是对jdbc一个简单的封装,也就是说相比于mybatis,jdbctemplate对jdbc的封装深度浅于mybatis,不知明白我的意思了吗?没明白,啧啧,那拉到博客最后直接看答案吧!我这里卖一个关子。


在spring-jdbc中,提供了一个类JdbcDaoSupport,这个类中把我们要引用的jdbcTemplate.java对象的所有通用操作都干完了,而我们要做的就是直接利用jdbcDaoSupport.java进行相应的dao操作就OK了。。。理解不了的话,建议直接看JdbcDaoSupport.java类的源码。
给出修改过的AccountDao.java源码:
AccountDao.java

package com.dao.impl;

import com.dao.IAccountDao;
import com.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

import java.util.List;

public class AccountDao extends JdbcDaoSupport implements IAccountDao {

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

    @Override
    public Account findById(Integer id) {
        List<Account> accounts = super.getJdbcTemplate().query(
                "select * from account where id = ?",
                new BeanPropertyRowMapper<>(Account.class),
                id
        );
        if (accounts.size() > 1) throw new RuntimeException("结果不唯一");
        return accounts.isEmpty() ? null : accounts.get(0);
    }

    @Override
    public List<Account> findAll() {
        List<Account> accounts = super.getJdbcTemplate().query(
                "select * from account",
                new BeanPropertyRowMapper<>(Account.class)
        );
        return accounts;
    }

    @Override
    public void deleteAccountById(Integer id) {
        super.getJdbcTemplate().update("delete from account where id = ?", id);
    }

    @Override
    public void deleteAccountByName(String name) {
        super.getJdbcTemplate().update("delete from account where name = ?", name);
    }
    @Override
    public void deleteAccount(Account account) {
        var id = account.getId();
        this.deleteAccountById(id);
    }

    @Override
    public Integer updateAccount(Account account) {
        var id = account.getId();
        super.getJdbcTemplate().update(
                "update account set name=?,money=? where id=?",
                account.getName(),
                account.getMoney(),
                account.getId()
        );
        var temp = this.findById(id);
        if (account.getMoney().equals(temp.getMoney()) && account.getName().equals(temp.getName())) {
            return id;
        } else {
            throw new RuntimeException("fail to update account's data");
        }
    }

    @Override
    public Integer addAccount(Account account) {
        super.getJdbcTemplate().update(
                "insert into account(`name`, `money`) values (?, ?)",
                account.getName(),
                account.getMoney()
        );
        var temp = this.findByName(account.getName());
        return temp == null ? null : temp.getId();
    }
}

作者不容易啊,把所有的dao都实现了,也就是说看这个的人可以在其他项目中直接用这个dao代码,而且每个方法,作者也做了测试。
IAccountService.java

package com.service;

public interface IAccountService {
    boolean transfer(String sourceName, String targetName, Float money);
}

AccountService.java

package com.service.impl;

import com.dao.impl.AccountDao;
import com.service.IAccountService;

public class AccountService implements IAccountService {

    private AccountDao accountDao;

    public AccountDao getAccountDao() {
        return accountDao;
    }

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

    /**
     * 做个转账功能吧
     *
     * @param sourceName 转出账户
     * @param targetName 转入账户
     * @param money      转账金额
     */
    @Override
    public boolean transfer(String sourceName, String targetName, Float money) {
        try{
            if (sourceName.equals(targetName)) {
                System.out.println("你写尼玛呢?账户名称一样,自己给自己转啊?");
                return false;
            }
            var sAccount = accountDao.findByName(sourceName);
            var tAccount = accountDao.findByName(targetName);
            sAccount.setMoney(sAccount.getMoney() - money);
            tAccount.setMoney(tAccount.getMoney() + money);
            accountDao.updateAccount(sAccount);
            accountDao.updateAccount(tAccount);
            return true;
        } catch (Throwable t){
            return false;
        }
    }
}

spring配置文件springConfig.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:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--引入JDBC配置文件-->
    <context:property-placeholder location="classpath:JdbcConnectMysql.properties"/>

    <!--配置数据源-->
    <!--
        我曾将p:username="root"和p:password="root"配置为p:username="${username}"和p:password="${password}"
        但是会报password(using the password: yes)错,这是驱动的问题,解析xml文件的问题,所以需要手动配置。
    -->
    <bean id="datasource"
          class="org.springframework.jdbc.datasource.DriverManagerDataSource"
          p:url="${url}"
          p:driverClassName="${driver}"
          p:username="root"
          p:password="root"
    />

    <bean id="accountDao"
          class="com.dao.impl.AccountDao"
          p:dataSource-ref="datasource"
    />

    <bean id="accountService"
          class="com.service.impl.AccountService"
          p:accountDao-ref="accountDao"
    />
</beans>

写一个AccountService类的测试类AccountServiceTest,如下:

package com.service.impl;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import static org.junit.Assert.*;

public class AccountServiceTest {

    private ApplicationContext applicationContext;
    private IAccountService accountService;
    @Before
    public void setUp() throws Exception {
        applicationContext = new ClassPathXmlApplicationContext("springConfig.xml");
        accountService = applicationContext.getBean("accountService", IAccountService.class);
    }

    @Test
    public void transfer() {
        if (accountService.transfer("aaa","bbb",100f)){
            System.out.print("success");
        } else {
            System.out.print("fail");
        }
    }
}

注:这个类是由idea自动生成的,但是内容要自己填,至于怎么自动生成,读者自行百度。(新手们,感谢我吧!!!)
测试结果我就不给了,读者自行测试。

以为到这里就结束了?no no no,如果在service中发生事务异常的话,转账这种高危操作就会失败。修改一下service层中AccountService代码,如下:
修改后的AccountService.java代码:

package com.service.impl;

import com.dao.impl.AccountDao;
import com.service.IAccountService;

public class AccountService implements IAccountService {

    private AccountDao accountDao;

    public AccountDao getAccountDao() {
        return accountDao;
    }

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

    /**
     * 做个转账功能吧
     *
     * @param sourceName 转出账户
     * @param targetName 转入账户
     * @param money      转账金额
     */
    @Override
    public boolean transfer(String sourceName, String targetName, Float money) {
        try{
            if (sourceName.equals(targetName)) {
                System.out.println("你写尼玛呢?账户名称一样,自己给自己转啊?");
                return false;
            }
            var sAccount = accountDao.findByName(sourceName);
            var tAccount = accountDao.findByName(targetName);
            sAccount.setMoney(sAccount.getMoney() - money);
            tAccount.setMoney(tAccount.getMoney() + money);
            accountDao.updateAccount(sAccount);

            int k = 1/0;// 这里会报一个运算异常,作者故意为之。
            
            accountDao.updateAccount(tAccount);
            return true;
        } catch (Throwable t){
            return false;
        }
    }
}

先给出数据库数据图如下所示:
在这里插入图片描述
执行测试类,控制台结果如下所示:
在这里插入图片描述
再刷新数据库数据如下所示:
在这里插入图片描述
发现只有aaa的数据变了,而bbb的数据没变,这是由于事务隔离性引起的。截取AccountService.java代码中的一段,如下所示:

var sAccount = accountDao.findByName(sourceName);//1
var tAccount = accountDao.findByName(targetName);//2
sAccount.setMoney(sAccount.getMoney() - money);//3
tAccount.setMoney(tAccount.getMoney() + money);//4
accountDao.updateAccount(sAccount);//5

int k = 1/0;//6

accountDao.updateAccount(tAccount);//7

上面1,2,3,4,5,6处没有任何问题,每执行一次SQL操作,就会开启一个事务(不是说一个事务只能执行一条语句,而是说上面每一次CRUD操作都会开启一个事务。)

在5处语句在事务中正常执行,而在6处发生异常,此时直接从6处跳到catch处,导致7处语句不能正常执行。要解决这个问题很简单,只需要开启事务配置就OK了。

在spring中有两大特性,IOC和AOP,可以利用AOP给每个业务操作加上事务控制。AOP可以有两种配置方式,一种annotation注解,一种是xml文件配置。(推荐用xml文件配置方式,原因很简单,annotation会出现方法advice增强乱序,final advice会在after advice执行(5.0.0版本后貌似问题解决了,但是还是建议用xml文件配置,因为明朗清晰)。)

修改springConfig.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:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/context
               https://www.springframework.org/schema/context/spring-context.xsd
               http://www.springframework.org/schema/aop
               https://www.springframework.org/schema/aop/spring-aop.xsd
               http://www.springframework.org/schema/tx
               http://www.springframework.org/schema/tx/spring-tx.xsd"
>

    <!--引入JDBC配置文件-->
    <context:property-placeholder location="classpath:JdbcConnectMysql.properties"/>

    <!--配置数据源-->
    <!--
        我曾将p:username="root"和p:password="root"配置为p:username="${username}"和p:password="${password}"
        但是会报password(using the password: yes)错,这是驱动的问题,解析xml文件出了问题,所以需要手动配置。
    -->
    <bean id="datasource"
          class="org.springframework.jdbc.datasource.DriverManagerDataSource"
          p:url="${url}"
          p:driverClassName="${driver}"
          p:username="root"
          p:password="root"
    />

    <bean id="accountDao"
          class="com.dao.impl.AccountDao"
          p:dataSource-ref="datasource"
    />

    <bean id="accountService"
          class="com.service.impl.AccountService"
          p:accountDao-ref="accountDao"
    />

    <!--1、配置事务管理器-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
          p:dataSource-ref="datasource"
    />

    <!--2、配置事务增强处理,其主要目的是为那些方法开启事务处理 -->
    <tx:advice id="txManager" transaction-manager="transactionManager">
        <!-- 配置事务的属性
        isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
        propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
        read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
        timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
        rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
        no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
        -->
        <tx:attributes>
            <tx:method name="*" read-only="false" timeout="60" propagation="REQUIRED"/>
            <tx:method name="find*" read-only="true" timeout="60" propagation="SUPPORTS"/>
        </tx:attributes>
    </tx:advice>

    <!--3、配置事务的aop管理,这个表示利用aop的特性来使用事务管理控制。-->
    <aop:config>
        <aop:pointcut id="p1" expression="execution(* com.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="txManager" pointcut-ref="p1"/>
    </aop:config>

</beans>

配置文件中的注释已经说明了任何问题,自行查看。
给出数据库初始数据如下所示:
在这里插入图片描述
执行测试类,其控制台结果如下所示:
在这里插入图片描述
怎么失败了?再去看看数据库的数据,如下所示:
在这里插入图片描述
嘶,事务控制没起作用?把作者拉出去斩了。
额,哈哈,在AccountService中,有try=>catch控制,而异常控制优于事务控制先执行,所以抓取异常后,直接跳过了事务控制。
去掉AccountService.java中的try=>catch控制,并把方法返回类型改为void,如下所示:

package com.service.impl;

import com.dao.impl.AccountDao;
import com.service.IAccountService;

public class AccountService implements IAccountService {

    private AccountDao accountDao;

    public AccountDao getAccountDao() {
        return accountDao;
    }

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

    /**
     * 做个转账功能吧
     *
     * @param sourceName 转出账户
     * @param targetName 转入账户
     * @param money      转账金额
     */
    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        if (sourceName.equals(targetName)) {
            System.out.println("你写尼玛呢?账户名称一样,自己给自己转啊?");
            return;
        }
        var sAccount = accountDao.findByName(sourceName);
        var tAccount = accountDao.findByName(targetName);
        sAccount.setMoney(sAccount.getMoney() - money);
        tAccount.setMoney(tAccount.getMoney() + money);
        accountDao.updateAccount(sAccount);

        int k = 1 / 0;// 此处一会注释掉

        accountDao.updateAccount(tAccount);
    }
}

再修改测试方法如下所示:

@Test
public void transfer() {
    accountService.transfer("aaa", "bbb", 100f);
    System.out.print("success");
}

执行测试方法,其结果如下所示(记住上面的那个数据,一个是900,一个是1000):
在这里插入图片描述
除零异常,意料之中,查看数据库数据,如下所示:
在这里插入图片描述
看看,前面的事务隔离性问题解决了。再将上述AccountService.java文件代码中的int k = 1 / 0;注释掉,继续执行测试类,其控制台结果如下所示:
在这里插入图片描述
查看数据库数据,如下所示:
在这里插入图片描述
事务控制成功了,哈哈!!!到此,文章结束了。上面那个关子的答案就是,封装深度越浅,执行越快,就是这个意思。


上述项目gitee地址:https://gitee.com/liu_liangyuan/spring_aop_transaction-manager_jdbc-template_using.git
如果你一个一个敲出来了,那么恭喜你,你的印象会很深,虽然很费事,但是你的所得不是别人所能比的。


贫穷绝无高尚可言,懦弱永无幸福可说,不成魔,怎得活?——良木

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值