(1)Spring提供的JdbcTemplate采用Template模式,提供了一些列以回调为特点的工具方法,目的是避免繁琐的try…catch语句。
(1)使用Spring操作JDBC的时候虽然方便,但是要在Spring中操作事务,就没必要手写JDBC事务,可以使用Spring提供的高级接口来操作事务。Spring提供了一个PlatformTransactionManager来表示事务管理器,所有的事务都由它负责管理。而事务由TransactionStatus表示。
(2)Spring为什么要抽象出PlatformTransactionManager和TransactionStatus?原因是JavaEE除了提供JDBC事务外,它还支持分布式事务JTA(Java Transaction API)。分布式事务是指多个数据源(比如多个数据库,多个消息系统)要在分布式环境实现事务的时候,应该怎么实现。分布式事务实现起来非常复杂,简单地来说就是通过一个分布式事务管理器实现两阶段提交,但本身数据库事务就不快,基于数据库事务实现的分布式事务就慢的难以忍受,所以使用率不高。Spring为了同时支持JDBC和JTA两种事务模型,就抽象出platformTransactionManager。因为我们代码只需要JDBC事务,因此,在AppConfig中,需要再定义一个PlatformTransactionManager对应Bean,它的实际类型是DataSourceTransactionManager;
(3)使用编程的方式使用Spring事务仍然比较繁琐,更好地方式是通过声明式事务来实现。使用声明式事务非常简单,除了在APPconfig中追加一个上述定义的PlatformTransactionManager外,再加一个@EnableTransactionManagement就可以启用声明式事务,然后对需要事务支持的方法,加一个@Transactional注解,或者直接在Bean的calss处加上,表示所有public方法都具有事务支持。
(1)默认情况下,如果发生了RuntimeException,Spring的声明式事务将自动回滚,在一个事务方法中,如果程序判断需要回滚事务,只需要抛出RuntimeException。如果要针对Checked Exception(必须被显式的抛出或者捕获的异常),需要在@Transactional注解中写出来,@Transactional(rollbackFor = {RuntimeException.class, IOException.class}),上述代码表示在抛出RuntimeException或者IOException时,事务将回滚。
(2)在使用事务时,明确事务边界很重要。对于声明式事务,它的事务边界就是方法开始和结束。
(3)Spring的声明式事务为事务传播定义了几个级别,默认传播级别就是REQUIRED,它的意思是,如果当前没有事务,就创建一个新事务,如果当前有事务,就加入到当前事务中执行。
(4)Spring使用声明式事务,最终也是通过执行JDBC事务来实现功能,那么一个事务方法是如何获取到当前是否存在事务?答案是使用ThreadLocal。Spring总是把JDBC相关Connection和TransactionStatus实例绑定到ThreadLocal。如果一个事务方法从ThreadLocal未取到事务,那么它会打开一个新的JDBC连接,同时开启一个新的事务,否则,它就会直接使用从ThreadLocal获取的JDBC连接以及TransactionStatus。因此,事务能正确传播的前提是,方法调用实在一个线程内才行。换句话说,事务只能在当前线程传播,无法跨线程传播。如果想要实现跨线程传播事务,就要想办法把当前线程绑定到ThreadLocal的Connection和TransactionStatus实例传递给新线程。
(1)在传统的多层应用程序中,通常是Web层调用业务层,业务层调用数据访问层。业务层负责处理各种业务逻辑,而数据访问层只负责对数据进行增删改查。因此,实现数据访问层就是用JdbcTemplate实现对数据库的操作。编写数据访问层的时候,可以使用DAO模式。DAO就是Data Access Object的缩写。Spring提供了一个JdbcDaoSupport类,用于简化DAO的实现。
(2)DAO模式就是一个简单的数据访问模式,是否使用DAO,根据实际情况决定,因为很多时候,直接在Service层操作数据库也是完全没有问题的。
(1)把关系数据库的表映射为Java对象的过程叫作ORM:Object Relational Mapping。ORM既可以把记录转换为Java对象,也可以把Java对象转化为行记录。使用JdbcTemplate配合RowMapper可以看作是最原始的ORM。如果要实现更自动化的ORM,可以选择成熟的ORM框架,例如Hibernate。
(2)如果一个JavaBean被用于映射,我们就标记一个@Entity。
(1)使用Hibernate或者JPA操作数据库时,这类ORM干的主要工作就是把ResultSet的每一行变成Java Bean,或者把Java Bean自动住哪环岛Insert 或者Update语句的参数中,从而实现ORM。而ORM框架之所以知道如何把行数据映射到JavaBean,是因为我们在JavaBean的属性上给了足够的注解作为元数据,ORM框架获取到JAVA Bean的注解后,就知道如何双向映射。那么ORM框架是如何跟踪Java Bean的修改,以便于在update()操作中更新必要的属性?答案是使用Proxy模式,从ORM框架读取的USER实例实际上并不是User类,而是代理类,代理类继承自User类,但是针对每个setter方法做了覆写:
public class UserProxy extends User {
boolean _isNameChanged;
public void setName(String name) {
super.setName(name);
_isNameChanged = true;
}
}
这样,代理类可以跟踪到每个属性的变化。
(2)针对一对多或多对一关系时,代理类可以直接通过getter方法查询数据库:
public class UserProxy extends User {
Session _session;
boolean _isNameChanged;
public void setName(String name) {
super.setName(name);
_isNameChanged = true;
}
/**
* 获取User对象关联的Address对象:
*/
public Address getAddress() {
Query q = _session.createQuery("from Address where userId = :userId");
q.setParameter("userId", this.getId());
List list = query.list();
return list.isEmpty() ? null : list(0);
}
}
为了实现这样的查询,UserProxy必须保存Hibernate的当前session。但是,当事务提交后,Session自动关闭,此时再获取getAddress()将无法访问数据库,或者获取的不是事务一致的数据库。因此,ORM框架总是引入了Attached/Detached
状态,表示当前此JavaBean到底实在Session的范围内,还是脱离了Session变成了一个游离对象。
(3)此外,Hibernate和JPA为了实现兼容多种数据库,它使用HQL和JPQL查询,经过一道转换,编程特定数据库的SQL,理论上这样可以做到无缝切换数据库,但这一层自动转换除了少许的性能开销外,给SQL级别的优化带来了很多麻烦。
(4)最后,ORM框架通常提供了缓存,并且还分为一级缓存和二级缓存,一级缓存是指在一个Session范围内的缓存,常见的情景是根据猪圈查询时,两次查询可以返回同一实例。二级缓存是指跨Session的缓存,一般默认为关闭,需要手动配置。二级缓存极大地增加了数据不一致性,原因在于SQL非常灵活,尝尝会导致以外的更新。例如:
// 线程1读取:
User user1 = session1.load(User.class, 123);
…
// 一段时间后,线程2读取:
User user2 = session2.load(User.class, 123);
当二级缓存生效的时候,两个线程读取的User实例是一样的,但是数据库对应的行记录完全可能被修改。
UPDATE users SET bonus = bonus + 100 WHERE createdAt <= ?
ORM无法判断id=123的用户是否收到该UPDATE语句影响。考虑到数据库通常会支持多个应用程序,此UPDATE语句可能由其他进程执行,ORM框架就更不知道了,我们把这种ORM跨甲称之为全自动ORM框架。
(5)对比Spring提供的JdbcTemplate,它和ORM框架相比,主要有几点差别:
查询后需要手动提供Mapper实例以便于把ResultSet的每一行变为Java对象
增删改查操作所需的参数列表,需要手动传入,即把User实例变为[user.id, user.name, user.email]这样的列表,比较麻烦。
但是JdbcTemplate的优势在于它的确定性:即每次读取操作一定是数据库操作而不是缓存,所执行的SQL是完全确定的,缺点就是代码比较繁琐,构造INSERT INTO users VALUES (?,?,?)更是复杂。
所以,介于全自动ORM如Hibernate和手写全部如JdbcTemplate之间,还有一种半自动的ORM,它只负责把ResultSet自动映射到Java Bean,或者自动填充Java Bean参数,但仍需自己写出SQL。MyBatis就是这样一种半自动化ORM框架。