53、spring整合jdbc
SpringFramework 有给我们提供一个数据源的模型类,它叫 DriverManagerDataSource 。
但是!DriverManagerDataSource 的底层就是 DriverManager.getConnection() 的操作,并没有连接池的设计,所以性能比较差,真正的开发中可千万不要用它!
spring整合jdbc的方法
将datasource传入jdbcTeamplate对象内,有编程式、xml式两种:
public static void main(String[] args) throws Exception {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring-dao?characterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
}
<?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="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring-dao?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
</beans>
jdbcTemplate 的 crud
使用 xml 方式注册IOC容器:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("jdbc/spring-jdbc.xml");
JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class);
-
insert 操作
除了execute操作之外,还可以使用
update命令,有返回值 0/1:jdbcTemplate.update("insert into tbl_user (name, tel) values (?, ?)", "heihei", "200"); -
update 操作
当然是
update命令啦:jdbcTemplate.update("update tbl_user set tel = ? where name = ?", "54321", "heihei"); -
delete 操作
还是
update命令:int row = jdbcTemplate.update("delete from tbl_user where name = ?", "heihei"); -
select 操作
首选方法:
query(String sql, RowMapper<T> rowMapper, Object... args)其中,
RowMapper接口 会将ResultSet(query结果)的一行数据封装为一个指定的类型(泛型中的类型)。一般选择 new 一个 **BeanPropertyRowMapper**实现类 :// 查全表 List<User> userList = jdbcTemplate.query("select * from tbl_user", new BeanPropertyRowMapper<>(User.class)); // 条件查询 ———— 查出表中 id 编号大于2的数据 List<User> userList = jdbcTemplate.query("select * from tbl_user where id > ?", new BeanPropertyRowMapper<>(User.class), 2); // 某条件下,取多个返回结果中的单个 List<User> userList = jdbcTemplate.query("select * from tbl_user where id = ?", new BeanPropertyRowMapper<>(User.class), 2); User user = userList.size() > 0 ? userList.get(0) : null;Tip:这个
userList不需要判空吗? 答案是不需要,JdbcTemplate底层封装结果集的逻辑:无论查出来有没有数据结果返回,List都先要初始化,它不可能为 null ,所以可以用它的size去做判断。queryForObject(),它可以直接指定返回数据要封装的类型。// 查数量 Long count = jdbcTemplate.queryForObject("select count(id) from tbl_user", Long.class);
54、JdbcTemplate在Dao层的使用方式
JdbcTemplate移入Dao层
上一章学习了JdbcTemplate的使用方法,但还没有集成到三层架构中。而数据层的访问应该要写到 Dao 层中。
之前在学习 JavaWeb 基础时,与数据库交互的 Dao 层,是使用 jdbc 的方式 ——> 整合了 SpringFramework 的 jdbc 之后,就应该使用 JdbcTemplate 了。
本章代码采用注解驱动的方式
其中:
-
UserDaoImpl中使用@Autowired注入了 JdbcTemplate;@Repository public class UserDaoImpl implements UserDao { @Autowired private JdbcTemplate jdbcTemplate; } -
在
JdbcConfiguration中注册了dataSource、jdbcTemplate 两个Bean,分别返回对应的对象:@Bean public DataSource dataSource(){ DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/spring-dao?characterEncoding=utf8"); dataSource.setUsername("root"); dataSource.setPassword("131313"); return dataSource; } @Bean public JdbcTemplate jdbcTemplate(){ return new JdbcTemplate(dataSource()); }
JdbcDaoSupport
上面 UserDaoImpl 的问题 —— 若有两个UserDao的实现类,因为每一个实现类中不可避免的需要注入JdbcTemplate,会造成重复注入的问题。
解决方案 ——
- 解决jdbcTemplate重复注册 :编写一个父类,声明自动注入的JdbcTemplate 为private,让子类用 get/set 方法 获取/设置。
- 解决需手动在配置类中注册jdbcTemplate : JdbcTemplate 是依赖
DataSource的,可以把DataSource传递给 父类,让它自己创建JdbcTemplate。
上面的解决方案其实就是 JdbcDaoSupport抽象类 的实现思路,我们使用时直接继承它就行了。核心代码:
public abstract class JdbcDaoSupport extends DaoSupport {
@Nullable
private JdbcTemplate jdbcTemplate;
public final void setDataSource(DataSource dataSource) {
if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
this.jdbcTemplate = createJdbcTemplate(dataSource);
initTemplateConfig();
}
}
但使用 JdbcDaoSupport 还需借助后置处理器,另外覆写一个方法:
public class JdbcDaoSupportPostProcessor implements BeanPostProcessor {
@Autowired
private DataSource dataSource;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof JdbcDaoSupport) {
JdbcDaoSupport daoSupport = (JdbcDaoSupport) bean;
daoSupport.setDataSource(dataSource);
}
return bean;
}
}
55、事务回顾 | 原生 JDBC 事务
事务
- 事务的四大特性: 原子性(事务的整体)、持久性(数据的完整)、隔离性(并发的隔离)、持久性(操作的结果)
- 事务并发操作中三种问题: 脏读、不可重复读、幻读
- 事物的隔离级别(由并发问题引出的): 读未提交、读已提交、可重复读、可串行化
原生jdbc事务
在日常开发中会遇到一些特殊的场景,它可能需要分段执行,如果出现异常后不需要全部回滚,这种情况就需要 jdbc 事务中的 保存点 了。
-
常用操作:
-
获取connection:
connection = dataSource.getConnection(); -
开启事务(关闭自动提交):
connection.setAutoCommit(false); -
预定义statement:
PreparedStatement statement = connection.prepareStatement("sql语句"); -
statement真正执行:
statement.executeUpdate(); -
事务提交:
connection.commit(); -
事务回滚:
connection.rollback(); -
关闭connection:
connection.close();
-
-
保存点:
- 设置保存点:
savepoint = connection.setSavepoint(); - 回滚到保存点:
connection.rollback(savepoint);
- 设置保存点:
56、编程式事务
事务应该放在业务层控制,所以接下来的代码中会有 Service 和 Dao 两部分。
| 注解 | 含义 |
|---|---|
| @Component | 最普通的组件,可以被注入到spring容器进行管理 |
| @Repository | 作用于持久层,@Repository注解类作为DAO对象,可以直接对数据库进行操作 |
| @Service | 作用于业务逻辑层 |
| @Controller | 作用于表现层(spring-mvc的注解) |
pipeline
-
整体架构:
其中,*Dao 要标注 `@Repository` 注解,Service 要标注 `@Service` 注解。*
-
注册相关组件
DataSourceTransactionManager:事务管理器,它负责控制事务TransactionTemplate:事务模板,使用它可以完成编程式事务<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> </bean>他们的关系为:
transactionTemplate包含transactionManager包含dataSource。 -
service使用事务模板
-
在 Service 中注入事务模板
TransactionTemplate; -
事务模板的使用方法是
transactionTemplate.execute(),自定义 service 中每一个事务方法都要使用execute()方法进行事务的提交,但其中一般只实现事务逻辑上的操作,与数据库的交互( sql语句的执行 )则由Dao类内实现的相应方法完成。-
这个
execute方法中需要传入一个TransactionCallback类型的对象,而这个TransactionCallback本身是一个函数式接口,所以可以lambda编程。@FunctionalInterface public interface TransactionCallback<T> { T doInTransaction(TransactionStatus status); }
transactionTemplate.execute(status ->{ userDao.save(user); int i = 1 / 0; List<User> userList = userDao.findAll(); System.out.println(userList); return null; }); -
-
-
TransactionCallback 的优化
execute() 源码如下:
public <T> T execute(TransactionCallback<T> action) throws TransactionException { // ...... T result; try { // 此处是lambda表达式的返回值 result = action.doInTransaction(status); } // ...... // 返回出去了 return result; } }为什么上面的 lambda 要返回null?因为lambda返回值等于execute方法的返回值。
除了可以使用
TransactionCallback接口的 lambda 实现之外,TransactionCallback接口加了一个抽象类TransactionCallbackWithoutResult:只要覆写doInTransaction方法即可,方法内部帮我们返回了 null,其他不变。
DataSourceTransactionManager
它是基于数据源的事务管理器。它实现的根接口 PlatformTransactionManager 有定义 commit 和 rollback 方法:
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
这个 commit 和 rollback 方法要传入一个 TransactionStatus 的参数,后面第 60 章 - Spring 事务控制模型再讲解。
TransactionTemplate
事务模板,它与前面咱学的 JdbcTemplate 在设计上是类似的,都是提供一个简单的模板来完成平时比较复杂的工作( JdbcTemplate 解决的是不需要再写那些复杂的 Statement 、ResultSet 等等)。
TransactionTemplate 本质上是一个 TransactionDefinition。它的核心方法是来自 TransactionOperations 接口定义的 execute 方法:(注意下面 else 块中的注释)
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// 业务代码出现异常,回滚事务
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// 业务代码出现异常,回滚事务
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
// try块没有出现异常,业务代码执行成功,提交事务
this.transactionManager.commit(status);
return result;
}
}
它提交和回滚事务的动作,就是拿的 TransactionManager 执行的 commit 和 rollback 方法。
57、声明式事务
编程式事务中,每个 Service 中都要注入 TransactionTemplate ;编写的所有方法都要使用 transactionTemplate.execute 方法完成事务的逻辑操作。太麻烦了!
\