Spring 数据库编程
Spring JDBC
要先引入相关依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.10.RELEASE</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> <scope>runtime</scope> </dependency>
|
配置数据源
applicationContext.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <?xml version="1.0" encoding="UTF-8"?> <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="http://www.springframework.org/schema/beans" 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="dataSource" class= "org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false"/> <property name="username" value="<username>"/> <property name="password" value="<password>"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="accountDao" class="com.itheima.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <bean id="transactionManager" class= "org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut expression="execution(* com.itheima.*.*(..))" id="txPointCut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config> </beans>
|
JDBC Template 的使用
execute()
直接执行 SQL 语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.itheima;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.JdbcTemplate;
public class TestJdbcTemplate {
public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); JdbcTemplate jdTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate"); jdTemplate.execute("create table account(" + "id int primary key auto_increment," + "username varchar(50)," + "balance double)"); System.out.println("账户表account创建成功!"); } }
|
编写实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.itheima;
public class Account { private Integer id; private String username; private Double balance;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public Double getBalance() { return balance; }
public void setBalance(Double balance) { this.balance = balance; }
public String toString() { return "Account [id=" + id + ", " + "username=" + username + ", balance=" + balance + "]"; } }
|
编写 DAO 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.itheima;
import java.util.List;
public interface AccountDao { public int addAccount(Account account);
public int updateAccount(Account account);
public int deleteAccount(int id);
public Account findAccountById(int id);
public List<Account> findAllAccount();
public void transfer(String outUser, String inUser, Double money); }
|
编写 DAO 实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.itheima;
import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public class AccountDaoImpl implements AccountDao { private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; }
}
|
update()
Insert 操作
AccountDao.addAccount(Account account)1 2 3 4 5 6 7 8 9 10 11 12 13
| public int addAccount(Account account) { String sql = "insert into account(username,balance) value(?,?)"; Object[] obj = new Object[]{ account.getUsername(), account.getBalance() }; int num = this.jdbcTemplate.update(sql, obj); return num; }
|
TestAddAccount.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.itheima;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAddAccount { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao"); Account account = new Account(); account.setUsername("tom"); account.setBalance(1000.00); int num = accountDao.addAccount(account); if (num > 0) { System.out.println("成功插入了" + num + "条数据!"); } else { System.out.println("插入操作执行失败!"); } } }
|
addAccount 方法中
int num = this.jdbcTemplate.update(sql, obj); 通过传入 SQL
语句和参数数组来执行插入操作,并返回受影响的记录条数,传入参数封装成了一个通用的
Obejct[] 数组,适用于各种 SQL 语句的参数传递。
这里的 update
语义上是所有数据库更新操作,包括插入、更新和删除等,并不仅限于数据库层面的更新操作。
Update 操作
AccountDao.updateAccount(Account account)1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public int updateAccount(Account account) { String sql = "update account set username=?,balance=? where id = ?"; Object[] params = new Object[]{ account.getUsername(), account.getBalance(), account.getId() }; int num = this.jdbcTemplate.update(sql, params); return num; }
|
TestUpdateAccount.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.itheima;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestUpdateAccount { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao"); Account account = new Account(); account.setId(1); account.setUsername("tom"); account.setBalance(2000.00); int num = accountDao.updateAccount(account); if (num > 0) { System.out.println("成功修改了" + num + "条数据!"); } else { System.out.println("修改操作执行失败!"); } } }
|
Delete 操作
AccountDao.deleteAccount(int id)1 2 3 4 5 6 7 8
| public int deleteAccount(int id) { String sql = "delete from account where id = ? "; int num = this.jdbcTemplate.update(sql, id); return num; }
|
TestDeleteAccount.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.itheima;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestDeleteAccount { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao"); int num = accountDao.deleteAccount(1); if (num > 0) { System.out.println("成功删除了" + num + "条数据!"); } else { System.out.println("删除操作执行失败!"); } } }
|
这里单个 id 参数传入 update
方法时实际上没有封装成 Object[]
query()
查单个
AccountDao.findAccountById(int id)1 2 3 4 5 6 7 8 9 10
| public Account findAccountById(int id) { String sql = "select * from account where id = ?"; RowMapper<Account> rowMapper = new BeanPropertyRowMapper<Account>(Account.class); return this.jdbcTemplate.queryForObject(sql, rowMapper, id); }
|
这里的 RowMapper 用于将查询结果的每一行封装成一个
Account 对象
TestFindAccountById.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.itheima;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FindAccountByIdTest { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao"); Account account = accountDao.findAccountById(1); System.out.println(account); } }
|
查多个
AccountDao.findAllAccount()1 2 3 4 5 6 7 8 9 10
| public List<Account> findAllAccount() { String sql = "select * from account"; RowMapper<Account> rowMapper = new BeanPropertyRowMapper<Account>(Account.class); return this.jdbcTemplate.query(sql, rowMapper); }
|
类似的通过 RowMapper 实现查询结果的转换
FindAllAccount.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.itheima;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class FindAllAccountTest { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao"); List<Account> account = accountDao.findAllAccount(); for (Account act : account) { System.out.println(act); } } }
|
Spring 事务管理
声明式事务管理
基于 XML 的声明式事务管理
applicationContext.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| ... <bean id="transactionManager" class= "org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/> </tx:attributes> </tx:advice>
<aop:config> <aop:pointcut expression="execution(* com.itheima.*.*(..))" id="txPointCut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config> ...
|
这里的 <aop:advisor> 不同于 part 4 的
<aop:aspect>,前者是将切入点和通知整合成一个切面,而后者是将切入点和通知分别定义成两个独立的组件,然后通过
<aop:aspect> 将它们整合成一个切面,advisor
通常用于点对点事务管理,aspect 通常用于面向切面编程
AccountDao.transfer(String outUser, String inUser, Double money)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public void transfer(String outUser, String inUser, Double money) { this.jdbcTemplate.update("update account set balance = balance +? " + "where username = ?", money, inUser); int i = 1 / 0; this.jdbcTemplate.update("update account set balance = balance-? " + "where username = ?", money, outUser); }
|
TransacationTest.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.itheima;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TransactionTest { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao"); accountDao.transfer("lisi", "zhangsan", 100.0); System.out.println("转账成功!"); } }
|
由于在 transfer
方法中模拟了一个运行时异常,导致事务回滚,最终账户余额没有发生变化,若没有开启事务,则第一个SQL语句会生效,第二个SQL语句会失败。
基于注解的声明式事务管理
applicationContext-annotation.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns="http://www.springframework.org/schema/beans" 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"> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false"/> <property name="username" value="<username>"/> <property name="password" value="<password>" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="accountDao" class="com.itheima.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <bean id="transactionManager" class= "org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
|
AccountDaoImpl.transfer(String outUser, String inUser, Double money)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false) public void transfer(String outUser, String inUser, Double money) { this.jdbcTemplate.update("update account set balance = balance +? " + "where username = ?", money, inUser); int i = 1 / 0; this.jdbcTemplate.update("update account set balance = balance-? " + "where username = ?", money, outUser); }
|
AnnotationTest.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.itheima;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-annotation.xml"); AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao"); accountDao.transfer("lisi", "zhangsan", 100.0); System.out.println("转账成功!"); } }
|
事务参数
Isolation(隔离级别)
DEFAULT: 使用数据库默认的隔离级别
READ_UNCOMMITTED: 读未提交,允许脏读、不可重复
READ_COMMITTED: 读已提交,禁止脏读,允许不可重复读
REPEATABLE_READ: 可重复读,禁止脏读、不可重复读
SERIALIZABLE: 串行化,禁止脏读、不可重复
Propagation(传播行为)
REQUIRED:
支持当前事务,如果当前没有事务,就新建一个事务
SUPPORTS:
支持当前事务,如果当前没有事务,就以非事务方式
MANDATORY:
支持当前事务,如果当前没有事务,就抛出异常
REQUIRES_NEW:
新建事务,如果当前存在事务,就把当前事务挂起
NOT_SUPPORTED:
以非事务方式执行,如果当前存在事务,就把当前事务挂起
NEVER:
以非事务方式执行,如果当前存在事务,就抛出异常
NESTED:
如果当前存在事务,则创建并在嵌套事务内执行,否则就新建一个事务