最近在学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
如果你一个一个敲出来了,那么恭喜你,你的印象会很深,虽然很费事,但是你的所得不是别人所能比的。
贫穷绝无高尚可言,懦弱永无幸福可说,不成魔,怎得活?——良木