PS: 有了Spring Boot ,看这些感觉要疯呀,感觉文档可能几年没更新过
事务
@Transactional注解
事务传播行为
事务监听器
@Component
public class MyComponent {
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}
}
DAO support
@Repository
public class JpaMovieFinder implements MovieFinder {
//JPA工厂
@PersistenceContext
private EntityManager entityManager;
// ...
}
@Repository
public class HibernateMovieFinder implements MovieFinder {
//Hibernate工厂
private SessionFactory sessionFactory;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
// ...
}
支持ORM框架
易于测试。Spring IoC的模式使得开发者可以轻易的替换Hibernate的SessionFactory实例,JDBC的DataSource实例,事务管理器,以及映射对象(如果有必要)的配置和实现。这一特点十分利于开发者对每个模块进行独立的测试。
泛化数据访问异常。Spring可以将ORM工具的异常封装起来,将所有异常(可以是受检异常)封装成运行时的DataAccessException体系。这一特性可以令开发者在合适的逻辑层上处理绝大多数不可修复的持久化异常,避免了大量的catch,throw和异常的声明。开发者还可以按需来处理这些异常。其中,JDBC异常(包括一些特定DB语言)都会被封装为相同的体系,意味着开发者即使使用不同的JDBC操作,基于不同的DB,也可以保证一致的编程模型。
通用的资源管理。Spring的应用上下文可以通过处理配置源的位置来灵活配置Hibernate的SessionFactory实例,JPA的EntityManagerFactory实例,JDBC的DataSource实例以及其他类似的资源。Spring的这一特性使得这些实例的配置十分易于管理和修改。同时,Spring还为处理持久化资源的配置提供了高效,易用和安全的处理方式。举个例子,有些代码使用了Hibernate需要使用相同的Session来确保高效性和正确的事务处理。Spring通过Hibernate的SessionFactory来获取当前的Session,来透明的将Session绑定到当前的线程。Srping为任何本地或者JTA事务环境解决了在使用Hibernate时碰到的一些常见问题。
集成事务管理。开发者可以通过@Transactional注解或在XML配置文件中显式配置事务AOP Advise拦截,将ORM代码封装在声明式的AOP方法拦截器中。事务的语义和异常处理(回滚等)都可以根据开发者自己的需求来定制。在后面的章节中,资源和事务管理中,开发者可以在不影响ORM相关代码的情况下替换使用不同的事务管理器。例如,开发者可以在本地事务和JTA之间进行交换,并在两种情况下具有相同的完整服务(如声明式事务)。而且,JDBC相关的代码在事务上完全和处理ORM部分的代码集成。这对于不适用于ORM的数据访问非常有用,例如批处理和BLOB流式传输,仍然需要与ORM操作共享常见事务。
JPA
Spring JPA支持提供了三种配置JPAEntityManagerFactory的方法,之后通过EntityManagerFactory来获取对应的实体管理器。
LocalEntityManagerFactoryBean(通常只有在简单的部署环境中使用此选项,例如在独立应用程序或者进行集成测试时,才会使用这种方式。):LocalEntityManagerFactoryBean创建一个适用于应用程序且仅使用JPA进行数据访问的简单部署环境的EntityManagerFactory。工厂bean会使用JPAPersistenceProvider自动检测机制,并且在大多数情况下,仅要求开发者指定持久化单元的名称:
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="myPersistenceUnit"/>
</bean>
</beans>
从JNDI中获取EntityManagerFactory(在部署到J2EE服务器时可以使用此选项。检查服务器的文档来了解如何将自定义JPA提供程序部署到服务器中,从而对服务器进行比默认更多的个性化定制。):
<beans>
<jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>
LocalContainerEntityManagerFactoryBean(在基于Spring的应用程序环境中使用此选项来实现完整的JPA功能。这包括诸如Tomcat的Web容器,以及具有复杂持久性要求的独立应用程序和集成测试。):会基于persistence.xml文件,dataSourceLookup策略和指定的loadTimeWeaver来创建一个PersistenceUnitInfo实例。因此,可以在JNDI之外使用自定义数据源并控制织入(weaving)过程。以下示例显示LocalContainerEntityManagerFactoryBean的典型Bean定义:
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="someDataSource"/>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
</beans>
//persistence.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
<mapping-file>META-INF/orm.xml</mapping-file>
<exclude-unlisted-classes/>
</persistence-unit>
</persistence>
Hibernate
//设置SessionFactory
<beans>
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="mappingResources">
<list>
<value>product.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.HSQLDialect
</value>
</property>
</bean>
</beans>
//或者jndi
<beans>
<jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>
//基于Hibernate API来实现DAO
@Component
public class ProductDaoImpl implements ProductDao {
private SessionFactory sessionFactory;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public Collection loadProductsByCategory(String category) {
return this.sessionFactory.getCurrentSession()
.createQuery("from test.Product product where product.category=?")
.setParameter(0, category)
.list();
}
}
//声明式事务划分
public class ProductServiceImpl implements ProductService {
private ProductDao productDao;
public void setProductDao(ProductDao productDao) {
this.productDao = productDao;
}
@Transactional
public void increasePriceOfAllProductsInCategory(final String category) {
List productsToChange = this.productDao.loadProductsByCategory(category);
// ...
}
@Transactional(readOnly = true)
public List<Product> findAllProducts() {
return this.productDao.findAllProducts();
}
}
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven/>
<bean id="myProductService" class="product.SimpleProductService">
<property name="productDao" ref="myProductDao"/>
</bean>
内嵌数据库
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("schema.sql")
.addScripts("user_data.sql", "country_data.sql")
.build();
}
}
Data access with JDBC
- JdbcTemplate 是经典的Spring JDBC访问方式,也是最常用的。这是“最基础”的方式、其他所有方式都是在 JdbcTemplate的基础之上封装的。
- NamedParameterJdbcTemplate 在原有JdbcTemplate的基础上做了一层包装支持命名参数特性、用于替代传统的JDBC“?”占位符。当SQL语句中包含多个参数时使用这种方式能有更好的可读性和易用性
- SimpleJdbcInsert和SimpleJdbcCall操作类主要利用JDBC驱动所提供的数据库元数据的一些特性来简化数据库操作配置。这种方式简化了编码、你只需要提供表或者存储过程的名字、以及和列名相匹配的参数Map。但前提是数据库需要提供足够的元数据。如果数据库没有提供这些元数据,需要开发者显式配置参数的映射关系。
- RDBMS对象的方式包含MappingSqlQuery, SqlUpdate和StoredProcedure,需要你在初始化应用数据访问层时创建可重用和线程安全的对象。这种方式设计上类似于JDO查询、你可以定义查询字符串,声明参数及编译查询语句。一旦完成这些工作之后,执行方法可以根据不同的传入参数被多次调用。
JdbcTemplate
Querying (SELECT)
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
"select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
new Object[]{1212L}, String.class);
Actor actor = this.jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
new Object[]{1212L},
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});
List<Actor> actors = this.jdbcTemplate.query(
"select first_name, last_name from t_actor",
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});
Updating (INSERT/UPDATE/DELETE)
this.jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");
this.jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
this.jdbcTemplate.update(
"delete from actor where id = ?",
Long.valueOf(actorId));
Other jdbcTemplate operations
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
NamedParameterJdbcTemplate
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(*) from T_ACTOR where first_name = :first_name";
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(*) from T_ACTOR where first_name = :first_name";
Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
public int countOfActors(Actor exampleActor) {
// notice how the named parameters match the properties of the above 'Actor' class
String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName";
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
数据源连接
DataSource
//DriverManagerDataSource
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");
//DBCP configuration
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");
//C3P0 configuration
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");
其他常用类
- DataSourceUtils类是一个方便有用的工具类,提供了从JNDI获取和关闭连接等有用的静态方法。它支持线程绑定的连接、例如:使用DataSourceTransactionManager的时候,将把数据库连接绑定到当前的线程上。
- AbstractDataSource是Spring DataSource实现的基础抽象类,封装了DataSource的基础通用功能。你可以继承AbstractDataSource自定义DataSource 实现。
- SingleConnectionDataSource实现了SmartDataSource接口、内部封装了一个在每次使用后都不会关闭的单一连接。显然,这种场景下无法支持多线程。这个类主要用于测试目的。例如,他使得测试代码能够脱离应用服务器,很方便的在单一的JNDI环境下调试。和DriverManagerDataSource相反,它总是重用相同的连接,这是为了避免在测试过程中创建过多的物理连接。
- DriverManagerDataSource类实现了标准的DataSource接口,可以通过Java Bean属性来配置原生的JDBC驱动,并且每次都返回一个新的连接。这个实现对于测试和JavaEE容器以外的独立环境比较有用,无论是作为一个在Spring IOC容器内的DataSource Bean,或是在单一的JNDI环境中。由于Connection.close()仅仅只是简单的关闭数据库连接,因此任何能够操作DataSource的持久层代码都能很好的工作。但是,使用JavaBean类型的连接池,比如commons-dbcp往往更简单、即使是在测试环境下也是如此,因此更推荐commons-dbcp。
- TransactionAwareDataSourceProxy会创建一个目标DataSource的代理,内部包装了DataSource,在此基础上添加了Spring事务管理功能。有点类似于JavaEE服务器中提供的JNDI事务数据源。
- DataSourceTransactionManager
DataSourceTransactionManager类实现了PlatformTransactionManager接口。它将JDBC连接从指定的数据源绑定到当前执行的线程中,
允许一个线程连接对应一个数据源。
应用代码需要通过DataSourceUtils.getConnection(DataSource) 来获取JDBC连接,而不是通过JavaEE标准的DataSource.getConnection来获取。它会抛出org.springframework.dao的运行时异常而不是编译时SQL异常。所有框架类像JdbcTemplate都默认使用这个策略。如果不需要和这个 DataSourceTransactionManager类一起使用,DataSourceUtils 提供的功能跟一般的数据库连接策略没有什么两样,因此它可以在任何场景下使用。
DataSourceTransactionManager支持自定义隔离级别,以及JDBC查询超时机制。为了支持后者,应用代码必须在每个创建的语句中使用JdbcTemplate或是调用DataSourceUtils.applyTransactionTimeout(..)方法
在单一的资源使用场景下它可以替代JtaTransactionManager,不需要要求容器去支持JTA。如果你严格遵循连接查找的模式的话、可以通过配置来做彼此切换。JTA本身不支持自定义隔离级别!
JDBC批处理
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
int[] updateCounts = jdbcTemplate.batchUpdate("update t_actor set first_name = ?, " +
"last_name = ? where id = ?",
new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, actors.get(i).getFirstName());
ps.setString(2, actors.get(i).getLastName());
ps.setLong(3, actors.get(i).getId().longValue());
}
public int getBatchSize() {
return actors.size();
}
});
return updateCounts;
}
// ... additional methods
}
public class JdbcActorDao implements ActorDao {
private NamedParameterTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
int[] updateCounts = namedParameterJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
batch);
return updateCounts;
}
// ... additional methods
}
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
List<Object[]> batch = new ArrayList<Object[]>();
for (Actor actor : actors) {
Object[] values = new Object[] {
actor.getFirstName(),
actor.getLastName(),
actor.getId()};
batch.add(values);
}
int[] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
batch);
return updateCounts;
}
// ... additional methods
}
利用SimpleJdbc类简化JDBC操作
SimpleJdbcInsert类和SimpleJdbcCall类主要利用了JDBC驱动所提供的数据库元数据的一些特性来简化数据库操作配置。这意味着可以在前端减少配置,当然你也可以覆盖或是关闭底层的元数据处理,在代码里面指定所有的细节。
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}
// ... additional methods
}
//自动生成主键
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}