秋天里的春天-spring笔记(四):事务和数据访问
2009-09-13 04:08
357 查看
1 事务管理
1.1 核心抽象
Spring的事务抽象是PlatformTransactionManager接口
public interface PlatformTransactionManager
{
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
getTransaction的参数TransactionDefinition用来描述事务的隔离级别,传播,超时,只读状态等。它返回一个TransactionStatus接口的实现:
public interface TransactionStatus
{
boolean isNewTransaction();
void setRollbackOnly();
boolean isRollbackOnly();
}
spring中常用的事务管理器(实现了PlatformTransactionManager接口):
l DataSourceTransactionManager - jdbc数据源事务管理器,用于jdbc事务,需要配置数据源
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
l JtaTransactionManager - jta全局事务管理器。用于应用服务器的jta环境。不需要显示配置数据源,而是由ejb容器全局管理。
<bean id="txManager"
class="org.springframework.transaction.jta.JtaTransactionManager" />
l HibernateTransactionManager - hibernate事务管理器。用于跟hibernate集成,需要显示配置一个SessionFactory
<bean id="txManager"
class="org.springframework.orm.hibernate.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
1.2 声明式事务管理
首先要在配置文件中加入对tx空间的支持:
<beans xmlns="http://www.springframework.org/schema/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"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
在spring的配置文件中,一个声明性事务的语义封装在<tx:advice/>标签中。id属性用来索引该事务,transaction-manager用来配置该事务的事务管理器(一个配置在容器中的PlatformTransactionManager接口实现。)。注意,如果有一个事务管理器被配置为“transactionManager”,那么transaction-manager属性不用被显示配置。
tx:advice中事务的默认行为设置如下:
l 事务传播设置 是 REQUIRED
l 隔离级别是DEFAULT
l 事务是 读/写
l 事务超时默认是依赖于事务系统的,或者事务超时没有被支持。
l 任何RuntimeException将触发事务回滚,但是任何checked Exception将不触发事务回滚
<tx:method />的设置:
一个完整的事务配置的例子:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="fooServiceOperation"
expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
</bean>
先定义一个事务语义,它使用txManager为事务管理器。在所有get方法上使用只读事务,其他方法使用缺省设置。然后通过一个aop切点将该事务作用于x.y.service.FooService的所有方法上。
1.3 基于注解的事务管理
@Transactional注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。方法上的注解将覆盖类上的注解。
@Transactional的缺省设置如下:
事务传播设置是 PROPAGATION_REQUIRED
事务隔离级别是 ISOLATION_DEFAULT
事务是 读/写
事务超时默认是依赖于事务系统的,或者事务超时没有被支持。
任何RuntimeException将触发事务回滚,但是任何checked Exception将不触发事务回滚
@Transactional的属性
在使用基于注解的事务时,必须在配置文件中指定事务的上下文配置。使用
1.4 编码风格的事务管理
假设在容器中配置好了名称为transactionManager的事务管理器,使用编程风格的事务管理如下例:
TransactionTemplate tt = new TransactionTemplate(
context.getBean( "transactionManager", PlatformTransactionManager.class )
) ;
tt.execute(
new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0)
{
// 需要在事务中执行的代码。。。。。。
}
}
);
2 集成hibernate
2.1 spring对集成hibernate的支持类
l HibernateTemplate-hibernate数据访问模版类
l HibernateDaoSupport-hibernate dao基类
l HibernateTransactionManage-hibernate事务管理器
l LocalSessionFactoryBean-hibeernate session工厂bean
2.2 SessionFactory的建立
l 配置数据源
<bean id="myDataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>com.microsoft.sqlserver.jdbc.SQLServerDriver</value>
</property>
<property name="url">
<value>jdbc:sqlserver://localhost:1433;DatabaseName=xfire</value>
</property>
<property name="username"><value>sa</value></property>
<property name="password"><value>1111.aaaa</value></property>
</bean>
l 配置Session工厂
<bean id="mySessionFactory"
class="org.springframework.orm.hibernate3.annotation.
AnnotationSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialet">
org.hibernate.dialect.SQLServerDialect
</prop>
<prop key="current_session_context_class">thread</prop>
</props>
</property>
<property name="annotatedClasses">
<list>
<value>com.plusir.platform.entity.EntityUser</value>
</list>
</property>
</bean>
2.3 HibernateTemplate类
spring为hibernate提供的模版类封装了常用的CRUD操作。如果要使用未封装的操作,可以给execute方法传递一个HibernateCallback的实现类来进行回调操作。模版类可以保证Session的正确打开和关闭,并且直接参与到事务。同时,HibernateTemplate类是线程安全的。
2.4 HibernateDaoSupport类
spring提供的Dao基类,子类可以调用getSessionFactory和getHibernateTemplate方法来取得支持。
2.5 编程风格的事务管理
假设有事务管理器为transactionManager。Dao对象的名字叫dao。
l 先实例化一个事务模版:
TransactionTemplate tt = new TransactionTemplate(transactionManager);
l 用TransactionCallback回调执行Dao对象的操作:
tt.execute(new TransactionCallbackWithoutResult(){
public void doInTransactionWithoutResult( TransactionStuts stuts )
{
dao.insert(....) ;
}
});
2.6 声明性事务管理
l 1.x风格
<bean id="hibernateSessionFactory"
class="org.springframework.orm.hibernate3.annotation.
AnnotationSessionFactoryBean">
<property name="dataSource">
<bean class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>
com.microsoft.sqlserver.jdbc.SQLServerDriver
</value>
</property>
<property name="url">
<value>
jdbc:sqlserver://localhost:1433;DatabaseName=xfire
</value>
</property>
<property name="username"><value>sa</value></property>
<property name="password"><value>1111.aaaa</value></property>
</bean>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialet">
org.hibernate.dialect.SQLServerDialect
</prop>
<prop key="current_session_context_class">thread</prop>
</props>
</property>
<property name="annotatedClasses">
<list>
<value>com.plusir.platform.entity.EntityUser</value>
</list>
</property>
</bean>
<bean id="persistenceHelper"
class="org.springframework.transaction.interceptor.
TransactionProxyFactoryBean">
<property name="transactionManager">
<bean class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<property name="sessionFactory" ref="hibernateSessionFactory" />
</bean>
</property>
<property name="target">
<bean class="com.plusir.framework.data.PersistenceHelperImpl">
<constructor-arg>
<bean class="org.springframework.orm.hibernate3.
HibernateTemplate">
<property name="sessionFactory"
ref="hibernateSessionFactory" />
</bean>
</constructor-arg>
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="remove">PROPAGATION_REQUIRED</prop>
<prop key="load">PROPAGATION_REQUIRED</prop>
<prop key="save">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
l 2.x风格
<bean id="hibernateSessionFactory"
class="org.springframework.orm.hibernate3.annotation.
AnnotationSessionFactoryBean">
<property name="dataSource">
<bean class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>
com.microsoft.sqlserver.jdbc.SQLServerDriver
</value>
</property>
<property name="url">
<value>
jdbc:sqlserver://localhost:1433;DatabaseName=xfire
</value>
</property>
<property name="username"><value>sa</value></property>
<property name="password"><value>1111.aaaa</value></property>
</bean>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialet">
org.hibernate.dialect.SQLServerDialect
</prop>
<prop key="current_session_context_class">thread</prop>
</props>
</property>
<property name="annotatedClasses">
<list>
<value>com.plusir.platform.entity.EntityUser</value>
</list>
</property>
</bean>
<bean id="txManager" class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="remove" />
<tx:method name="load" />
<tx:method name="save" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut="target(com.plusir.framework.data.
PersistenceHelperImpl)" />
</aop:config>
<bean name=" persistenceHelper"
class=" com.plusir.framework.data.PersistenceHelperImpl" />
3 Spring中的JDBC封装
3.1 工作模式
l JdbcTemplate - 这是经典的也是最常用的Spring对于JDBC访问的方案。这也是最低级别的封装, 其他的工作模式事实上在底层使用了JdbcTemplate作为其底层的实现基础。
l NamedParameterJdbcTemplate - 对JdbcTemplate做了封装,提供了更加便捷的基于命名参数的使用方式而不是传统的JDBC所使用的“?”作为参数的占位符。
l SimpleJdbcTemplate - 这个类结合了JdbcTemplate和NamedParameterJdbcTemplate的最常用的功能。
l SimpleJdbcInsert 和 SimpleJdbcCall - 这两个类可以充分利用数据库元数据的特性来简化配置。
l RDBMS 对象包括MappingSqlQuery, SqlUpdate and StoredProcedure - 这种方式允许你在初始化你的数据访问层时创建可重用并且线程安全的对象。
3.2 Spring JDBC包结构
l org.springframework.jdbc.core包由JdbcTemplate类以及相关的回调接口(callback interface)和类组成。
l org.springframework.jdbc.core.simple包则包含了 SimpleJdbcTemplate类以及相关的SimpleJdbcInsert类和SimpleJdbcCall 类。
l org.springframework.jdbc.core.namedparam子包则包含了NamedParameterJdbcTemplate 类以及其相关的支持类。
l org.springframework.jdbc.datasource包提供了一些工具类来简化对DataSource的访问。
l org.springframework.jdbc.object包包含了一些类,用于将RDBMS查询、更新以及存储过程表述为一些可重用的、线程安全的对象。
l org.springframework.jdbc.support包定义了SQLException转化类以及一些其他的工具类。
3.3 JdbcTemplate类
需要一个DataSource引用来实例化。该类执行的sql将以DEBUG的形式输出,输出器是该类的完全限定名。JdbcTemplate是线程安全的。
查询单值可以使用queryForInt或者queryForObject等方法。查询列表可以使用queryForList方法,该方法返回一个List,其中每项是一个行数据,每一个项是一个Map实例,一个entry代表一个列。添删改操作使用update方法来实现。execute方法可以被用作执行任何类型的SQL,甚至DDL语句。
将一个实现了BatchPreparedStatementSetter接口的类实例传递给batchUpdate方法可以进行批量操作。
3.4 NamedParameterJdbcTemplate类
NamedParameterJdbcTemplate类为JDBC操作增加了命名参数的特性支持,而不是传统的使用('?')作为参数的占位符。NamedParameterJdbcTemplate类对JdbcTemplate类进行了封装,访问一些只有在JdbcTemplate中拥有的功能,你需要使用getJdbcOperations()方法来进行访问。NamedParameterJdbcTemplate类封装命名参数主要通过实现SqlParameterSource接口的两个类来完成-MapSqlParameterSource和BeanPropertySqlParameterSource。
MapSqlParameterSource以键/值对的形式封装命名参数。BeanPropertySqlParameterSource类对传统的Java进行了封装并且使用了JavaBean的属性作为参数的名称和值。
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public Actor findActor(long id) {
String sql = "select id, first_name, last_name from T_ACTOR where id = ?";
RowMapper mapper = new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setId(rs.getLong("id"));
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
};
return (Actor) jdbcTemplate.queryForObject(sql, mapper, new Object[] {Long.valueOf(id)});
}
3.5 SimpleJdbcTemplate类
SimpleJdbcTemplate类是对JdbcTemplate类进行的封装,从而可以充分利用Java 5所带来的varargs和autoboxing等特性。
private SimpleJdbcTemplate simpleJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
public Actor findActor(long id) {
String sql = "select id, first_name, last_name from T_ACTOR where id = ?";
ParameterizedRowMapper<Actor> mapper = new ParameterizedRowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setId(rs.getLong("id"));
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
};
return this.simpleJdbcTemplate.queryForObject(sql, mapper, id);
}
利用SimpleJdbcTemplate类进行批量操作无需实现一个特定的接口,你只需要提供所有在调用过程中要用到的参数,框架会遍历这些参数值,并使用内置的prepared statement类进行批量操作。API将根据你是否使用命名参数而有所不同。对于使用命名参数的情况,你需要提供一个SqlParameterSource的数组, 其中的每个元素将将作为批量操作的参数。 你可以使用SqlParameterSource.createBatch方法,通过传入一个JavaBean的数组或者一个包含了参数键值对的Map数组来创建这个数组。
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
int[] updateCounts = simpleJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
batch);
return updateCounts;
}
// ... additional methods
}
3.6 Spring的数据库连接
l DataSourceUtils作为一个帮助类提供易用且强大的数据库访问能力,我们可以使用该类提供的静态方法从JNDI获取数据库连接以及在必要的时候关闭之。
l SmartDataSource是DataSource接口的一个扩展,用来提供数据库连接。
l AbstractDataSource是一个实现了DataSource接口的abstract基类。
l SingleConnectionDataSource是SmartDataSource接口的一个实现,其内部包装了一个单连接。该连接在使用之后将不会关闭,很显然它不能在多线程的环境下使用。
l DriverManagerDataSource类实现了SmartDataSource接口。可以使用bean properties来设置JDBC Driver属性,该类每次返回的都是一个新的连接。
l TransactionAwareDataSourceProxy作为目标DataSource的一个代理,在对目标DataSource包装的同时,还增加了Spring的事务管理能力。
l DataSourceTransactionManager类是PlatformTransactionManager接口的一个实现,用于处理单JDBC数据源。 它将从指定DataSource取得的JDBC连接绑定到当前线程,因此它也支持了每个数据源对应到一个线程。
l NativeJdbcExtractor如果你要访问特定厂商提供的与标准JDBC的API不同的JDBC对象,你可以配置一个包含NativeJdbcExtractor的JdbcTemplate或者OracleLobHandler。
3.7 使用SimpleJdbcInsert插入数据
SimpleJdbcInsert类的构建需要一个数据源,创建一个新的实例后,通过withTableName方法设置table名,也可以用usingColumns方法指定列名,下例构造了一个更新器并指定其更新表”t_actor”:
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
下例指定更新器只更新名称列和编号列:
insert = inser. usingColumns("name", "code");
可以通过三种方式给更新器指定值
l 使用Map实例:
Map<String, Object> paras = new HashMap<String, Object>();
paras.put("name", "name");
paras.put("code", "code");
insert.execute(paras);
l 使用MapSqlParameterSource:
SqlParameterSource paras = new MapSqlParameterSource()
.addValue("name", "name")
.addValue("code", "code");
insert.execute(paras);
l 使用BeanPropertySqlParameterSource:
private class UpdateBean
{
public UpdateBean(String name, String code)
{
this.name = name;
this.code = code;
}
public String getName()
{
return this.name;
}
public void setName(String name)
{
this.name = name;
}
public String getCode()
{
return this.code;
}
public void setCode(String code)
{
this.name = code;
}
private String name;
private String code;
}
UpdateBean updateBean = new UpdateBean("name", "code");
SqlParameterSource paras = new BeanPropertySqlParameterSource(updateBean);
insert.execute(paras);
3.8 使用SimpleJdbcCall调用存储过程
SimpleJdbcCall类的构建同样需要一个数据源,创建一个新的实例后,通过withProcedureName方法设置要调用的存储过程名,也可以用declareParameters方法定义调用参数(需要先调用withoutProcedureColumnMetaDataAccess方法指定数据库忽略所有的元数据处理并使用显示声明的参数。),下例构造了一个调用器并指定其调用过程"read_actor":
SimpleJdbcCall call = new SimpleJdbcCall(dataSource).withProcedureName("read_actor");
下例指定调用器定义了一组参数:
call = call.withProcedureName("read_actor")
. withoutProcedureColumnMetaDataAccess()
.declareParameters(
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
new SqlOutParameter("out_last_name", Types.VARCHAR),
new SqlOutParameter("out_birth_date", Types.DATE)
);
另外一种情况是,其中的某些参数值具有默认的返回值,我们需要在返回值中指定这些返回值。为了实现这个特性,我们可以使用useInParameterNames来指定一组需要被包含的参数名称。传递参数同样可以使用Map或者SqlParameterSource接口的实现类。同时,execute方法会返回一个Map类型的对象,里面是用返回参数名称索引的返回值的集合。
调用内置函数使用withFunctionName方法来完成。同时有一个叫做executeFunction的方法,将获得特定的Java类型的函数调用的返回值。另外有一个类似的便捷方法executeObject用于存储过程,但是他只能处理单个返回值的情况。
对于返回游标的情况,可以使用returningResultSet方法:
procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_all_actors")
.returningResultSet(
"actors",
ParameterizedBeanPropertyRowMapper.newInstance(Actor.class)
);
Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
return (List) m.get("actors");
3.9 MappingSqlQuery类
是一个可重用的查询抽象类,其具体类必须实现mapRow(ResultSet, int)抽象方法来将结果集中的每一行转换成Java对象。
private class CustomerMappingQuery extends MappingSqlQuery
{
public CustomerMappingQuery(DataSource ds) {
super(ds, "SELECT id, name FROM customer WHERE id = ?");
super.declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}
public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
Customer cust = new Customer();
cust.setId((Integer) rs.getObject("id"));
cust.setName(rs.getString("name"));
return cust;
}
}
3.10 SqlUpdate类
SqlUpdate类封装了一个可重复使用的SQL更新操作。该类提供了一系列update方法。 它是一个具体的类,通过在SQL语句中定义参数,不需要通过继承来实现定制。
public class UpdateCreditRating extends SqlUpdate
{
public UpdateCreditRating(DataSource ds)
{
setDataSource(ds);
setSql("update customer set credit_rating = ? where id = ?");
declareParameter(new SqlParameter(Types.NUMERIC));
declareParameter(new SqlParameter(Types.NUMERIC));
compile();
}
public int run(int id, int rating)
{
Object[] params =
new Object[] {
new Integer(rating),
new Integer(id)};
return update(params);
}
}
3.11 StoredProcedure类
StoredProcedure类是一个抽象基类,该类提供了多种protected的execute方法。从父类继承的sql属性用来指定RDBMS存储过程的名字。
private class MyStoredProcedure extends StoredProcedure
{
private static final String SQL = "sysdate";
public MyStoredProcedure(DataSource ds)
{
setDataSource(ds);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}
public Map execute()
{
return execute(new HashMap());
}
}
如果你需要给存储过程传输入参数, 则需要提供一个指定类型的execute(..)方法,该方法将调用基类的protected execute(Map parameters)方法。
3.12 SqlFunction类
SqlFunction类封装了一个SQL“函数”包装器(wrapper),该包装器适用于查询并返回一个单行结果集。SqlFunction是一个具体类,其用法是创建该类的实例,然后声明SQL语句以及参数就可以调用相关的run方法去多次执行函数。
public int countRows()
{
SqlFunction sf = new SqlFunction(
dataSource,
"select count(*) from mytable"
);
sf.compile();
return sf.run();
}
1.1 核心抽象
Spring的事务抽象是PlatformTransactionManager接口
public interface PlatformTransactionManager
{
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
getTransaction的参数TransactionDefinition用来描述事务的隔离级别,传播,超时,只读状态等。它返回一个TransactionStatus接口的实现:
public interface TransactionStatus
{
boolean isNewTransaction();
void setRollbackOnly();
boolean isRollbackOnly();
}
spring中常用的事务管理器(实现了PlatformTransactionManager接口):
l DataSourceTransactionManager - jdbc数据源事务管理器,用于jdbc事务,需要配置数据源
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
l JtaTransactionManager - jta全局事务管理器。用于应用服务器的jta环境。不需要显示配置数据源,而是由ejb容器全局管理。
<bean id="txManager"
class="org.springframework.transaction.jta.JtaTransactionManager" />
l HibernateTransactionManager - hibernate事务管理器。用于跟hibernate集成,需要显示配置一个SessionFactory
<bean id="txManager"
class="org.springframework.orm.hibernate.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
1.2 声明式事务管理
首先要在配置文件中加入对tx空间的支持:
<beans xmlns="http://www.springframework.org/schema/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"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
在spring的配置文件中,一个声明性事务的语义封装在<tx:advice/>标签中。id属性用来索引该事务,transaction-manager用来配置该事务的事务管理器(一个配置在容器中的PlatformTransactionManager接口实现。)。注意,如果有一个事务管理器被配置为“transactionManager”,那么transaction-manager属性不用被显示配置。
tx:advice中事务的默认行为设置如下:
l 事务传播设置 是 REQUIRED
l 隔离级别是DEFAULT
l 事务是 读/写
l 事务超时默认是依赖于事务系统的,或者事务超时没有被支持。
l 任何RuntimeException将触发事务回滚,但是任何checked Exception将不触发事务回滚
<tx:method />的设置:
属性 | 是否需要 | 默认值 | 描述 |
name | 是 | 与事务属性关联的方法名。通配符(*)可以用来指定一批关联到相同的事务属性的方法。 如:'get*'、 'handle*'、 'on*Event'等等。 | |
propagation | 不 | REQUIRED | 事务传播行为 |
isolation | 不 | DEFAULT | 事务隔离级别 |
timeout | 不 | -1 | 事务超时的时间(以秒为单位) |
read-only | 不 | false | 事务是否只读? |
rollback-for | 不 | 将被触发进行回滚的 Exception(s);以逗号分开。 如: 'com.foo.MyBusinessException,ServletException' | |
no-rollback-for | 不 | 不 被触发进行回滚的 Exception(s);以逗号分开。 如: 'com.foo.MyBusinessException,ServletException' |
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="fooServiceOperation"
expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
</bean>
先定义一个事务语义,它使用txManager为事务管理器。在所有get方法上使用只读事务,其他方法使用缺省设置。然后通过一个aop切点将该事务作用于x.y.service.FooService的所有方法上。
1.3 基于注解的事务管理
@Transactional注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。方法上的注解将覆盖类上的注解。
@Transactional的缺省设置如下:
事务传播设置是 PROPAGATION_REQUIRED
事务隔离级别是 ISOLATION_DEFAULT
事务是 读/写
事务超时默认是依赖于事务系统的,或者事务超时没有被支持。
任何RuntimeException将触发事务回滚,但是任何checked Exception将不触发事务回滚
@Transactional的属性
属性 | 类型 | 描述 |
propagation | 枚举型:Propagation | 可选的传播性设置 |
isolation | 枚举型:Isolation | 可选的隔离性级别(默认值:ISOLATION_DEFAULT) |
readOnly | 布尔型 | 读写型事务 vs. 只读型事务 |
timeout | int型,以秒为单位 | 事务超时 |
rollbackFor | 一组 Class 类的实例,必须是Throwable 的子类 | 一组异常类,遇到时 必须 进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException的子类)才进行事务回滚。 |
rollbackForClassname | 一组 Class 类的名字,必须是Throwable的子类 | 一组异常类名,遇到时 必须 进行回滚 |
noRollbackFor | 一组 Class 类的实例,必须是Throwable 的子类 | 一组异常类,遇到时 必须不 回滚。 |
noRollbackForClassname | 一组 Class 类的名字,必须是Throwable 的子类 | 一组异常类,遇到时 必须不 回滚 |
tx:annotation-driven
标签。
属性 | 默认值 | 描述 |
transaction-manager | transactionManager | 使用的事务管理器的名字。只有像在上面的例子那样,事务管理器不是 transactionManager的情况下才需要。 |
mode | proxy | 默认的模式“proxy”会用Spring的AOP框架来代理注解过的bean(就像在前面讨论过的那样, 下面代理的语义只对通过代理传递过来的方法调用起效)。 另一种可行的模式"aspectj"会使用Spring的AspectJ事务切面来编织类(通过修改目标对象的字节码应用到任何方法调用上)。 AspectJ织入需要在classpath中有spring-aspects.jar这个文件,并且启用装载时织入 (或者编译时织入)。 |
proxy-target-class | false | 只对代理模式有效。决定为那些使用了@Transactional注解的类创建何种事务代理。 如果 " proxy-target-class" 属性被设为 " true", 那么基于类的代理就会被创建。如果 " proxy-target-class"属性被设为" false" 或者没设,那么基于接口的标准JDK代理就会被创建 |
order | Ordered.LOWEST_PRECEDENCE | 定义事务通知的顺序会作用到使用@Transactional注解的bean上。请注意如果不指定任何顺序将会把决定权交给AOP子系统。 |
假设在容器中配置好了名称为transactionManager的事务管理器,使用编程风格的事务管理如下例:
TransactionTemplate tt = new TransactionTemplate(
context.getBean( "transactionManager", PlatformTransactionManager.class )
) ;
tt.execute(
new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0)
{
// 需要在事务中执行的代码。。。。。。
}
}
);
2 集成hibernate
2.1 spring对集成hibernate的支持类
l HibernateTemplate-hibernate数据访问模版类
l HibernateDaoSupport-hibernate dao基类
l HibernateTransactionManage-hibernate事务管理器
l LocalSessionFactoryBean-hibeernate session工厂bean
2.2 SessionFactory的建立
l 配置数据源
<bean id="myDataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>com.microsoft.sqlserver.jdbc.SQLServerDriver</value>
</property>
<property name="url">
<value>jdbc:sqlserver://localhost:1433;DatabaseName=xfire</value>
</property>
<property name="username"><value>sa</value></property>
<property name="password"><value>1111.aaaa</value></property>
</bean>
l 配置Session工厂
<bean id="mySessionFactory"
class="org.springframework.orm.hibernate3.annotation.
AnnotationSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialet">
org.hibernate.dialect.SQLServerDialect
</prop>
<prop key="current_session_context_class">thread</prop>
</props>
</property>
<property name="annotatedClasses">
<list>
<value>com.plusir.platform.entity.EntityUser</value>
</list>
</property>
</bean>
2.3 HibernateTemplate类
spring为hibernate提供的模版类封装了常用的CRUD操作。如果要使用未封装的操作,可以给execute方法传递一个HibernateCallback的实现类来进行回调操作。模版类可以保证Session的正确打开和关闭,并且直接参与到事务。同时,HibernateTemplate类是线程安全的。
2.4 HibernateDaoSupport类
spring提供的Dao基类,子类可以调用getSessionFactory和getHibernateTemplate方法来取得支持。
2.5 编程风格的事务管理
假设有事务管理器为transactionManager。Dao对象的名字叫dao。
l 先实例化一个事务模版:
TransactionTemplate tt = new TransactionTemplate(transactionManager);
l 用TransactionCallback回调执行Dao对象的操作:
tt.execute(new TransactionCallbackWithoutResult(){
public void doInTransactionWithoutResult( TransactionStuts stuts )
{
dao.insert(....) ;
}
});
2.6 声明性事务管理
l 1.x风格
<bean id="hibernateSessionFactory"
class="org.springframework.orm.hibernate3.annotation.
AnnotationSessionFactoryBean">
<property name="dataSource">
<bean class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>
com.microsoft.sqlserver.jdbc.SQLServerDriver
</value>
</property>
<property name="url">
<value>
jdbc:sqlserver://localhost:1433;DatabaseName=xfire
</value>
</property>
<property name="username"><value>sa</value></property>
<property name="password"><value>1111.aaaa</value></property>
</bean>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialet">
org.hibernate.dialect.SQLServerDialect
</prop>
<prop key="current_session_context_class">thread</prop>
</props>
</property>
<property name="annotatedClasses">
<list>
<value>com.plusir.platform.entity.EntityUser</value>
</list>
</property>
</bean>
<bean id="persistenceHelper"
class="org.springframework.transaction.interceptor.
TransactionProxyFactoryBean">
<property name="transactionManager">
<bean class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<property name="sessionFactory" ref="hibernateSessionFactory" />
</bean>
</property>
<property name="target">
<bean class="com.plusir.framework.data.PersistenceHelperImpl">
<constructor-arg>
<bean class="org.springframework.orm.hibernate3.
HibernateTemplate">
<property name="sessionFactory"
ref="hibernateSessionFactory" />
</bean>
</constructor-arg>
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="remove">PROPAGATION_REQUIRED</prop>
<prop key="load">PROPAGATION_REQUIRED</prop>
<prop key="save">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
l 2.x风格
<bean id="hibernateSessionFactory"
class="org.springframework.orm.hibernate3.annotation.
AnnotationSessionFactoryBean">
<property name="dataSource">
<bean class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>
com.microsoft.sqlserver.jdbc.SQLServerDriver
</value>
</property>
<property name="url">
<value>
jdbc:sqlserver://localhost:1433;DatabaseName=xfire
</value>
</property>
<property name="username"><value>sa</value></property>
<property name="password"><value>1111.aaaa</value></property>
</bean>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialet">
org.hibernate.dialect.SQLServerDialect
</prop>
<prop key="current_session_context_class">thread</prop>
</props>
</property>
<property name="annotatedClasses">
<list>
<value>com.plusir.platform.entity.EntityUser</value>
</list>
</property>
</bean>
<bean id="txManager" class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="remove" />
<tx:method name="load" />
<tx:method name="save" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut="target(com.plusir.framework.data.
PersistenceHelperImpl)" />
</aop:config>
<bean name=" persistenceHelper"
class=" com.plusir.framework.data.PersistenceHelperImpl" />
3 Spring中的JDBC封装
3.1 工作模式
l JdbcTemplate - 这是经典的也是最常用的Spring对于JDBC访问的方案。这也是最低级别的封装, 其他的工作模式事实上在底层使用了JdbcTemplate作为其底层的实现基础。
l NamedParameterJdbcTemplate - 对JdbcTemplate做了封装,提供了更加便捷的基于命名参数的使用方式而不是传统的JDBC所使用的“?”作为参数的占位符。
l SimpleJdbcTemplate - 这个类结合了JdbcTemplate和NamedParameterJdbcTemplate的最常用的功能。
l SimpleJdbcInsert 和 SimpleJdbcCall - 这两个类可以充分利用数据库元数据的特性来简化配置。
l RDBMS 对象包括MappingSqlQuery, SqlUpdate and StoredProcedure - 这种方式允许你在初始化你的数据访问层时创建可重用并且线程安全的对象。
3.2 Spring JDBC包结构
l org.springframework.jdbc.core包由JdbcTemplate类以及相关的回调接口(callback interface)和类组成。
l org.springframework.jdbc.core.simple包则包含了 SimpleJdbcTemplate类以及相关的SimpleJdbcInsert类和SimpleJdbcCall 类。
l org.springframework.jdbc.core.namedparam子包则包含了NamedParameterJdbcTemplate 类以及其相关的支持类。
l org.springframework.jdbc.datasource包提供了一些工具类来简化对DataSource的访问。
l org.springframework.jdbc.object包包含了一些类,用于将RDBMS查询、更新以及存储过程表述为一些可重用的、线程安全的对象。
l org.springframework.jdbc.support包定义了SQLException转化类以及一些其他的工具类。
3.3 JdbcTemplate类
需要一个DataSource引用来实例化。该类执行的sql将以DEBUG的形式输出,输出器是该类的完全限定名。JdbcTemplate是线程安全的。
查询单值可以使用queryForInt或者queryForObject等方法。查询列表可以使用queryForList方法,该方法返回一个List,其中每项是一个行数据,每一个项是一个Map实例,一个entry代表一个列。添删改操作使用update方法来实现。execute方法可以被用作执行任何类型的SQL,甚至DDL语句。
将一个实现了BatchPreparedStatementSetter接口的类实例传递给batchUpdate方法可以进行批量操作。
3.4 NamedParameterJdbcTemplate类
NamedParameterJdbcTemplate类为JDBC操作增加了命名参数的特性支持,而不是传统的使用('?')作为参数的占位符。NamedParameterJdbcTemplate类对JdbcTemplate类进行了封装,访问一些只有在JdbcTemplate中拥有的功能,你需要使用getJdbcOperations()方法来进行访问。NamedParameterJdbcTemplate类封装命名参数主要通过实现SqlParameterSource接口的两个类来完成-MapSqlParameterSource和BeanPropertySqlParameterSource。
MapSqlParameterSource以键/值对的形式封装命名参数。BeanPropertySqlParameterSource类对传统的Java进行了封装并且使用了JavaBean的属性作为参数的名称和值。
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public Actor findActor(long id) {
String sql = "select id, first_name, last_name from T_ACTOR where id = ?";
RowMapper mapper = new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setId(rs.getLong("id"));
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
};
return (Actor) jdbcTemplate.queryForObject(sql, mapper, new Object[] {Long.valueOf(id)});
}
3.5 SimpleJdbcTemplate类
SimpleJdbcTemplate类是对JdbcTemplate类进行的封装,从而可以充分利用Java 5所带来的varargs和autoboxing等特性。
private SimpleJdbcTemplate simpleJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
public Actor findActor(long id) {
String sql = "select id, first_name, last_name from T_ACTOR where id = ?";
ParameterizedRowMapper<Actor> mapper = new ParameterizedRowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setId(rs.getLong("id"));
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
};
return this.simpleJdbcTemplate.queryForObject(sql, mapper, id);
}
利用SimpleJdbcTemplate类进行批量操作无需实现一个特定的接口,你只需要提供所有在调用过程中要用到的参数,框架会遍历这些参数值,并使用内置的prepared statement类进行批量操作。API将根据你是否使用命名参数而有所不同。对于使用命名参数的情况,你需要提供一个SqlParameterSource的数组, 其中的每个元素将将作为批量操作的参数。 你可以使用SqlParameterSource.createBatch方法,通过传入一个JavaBean的数组或者一个包含了参数键值对的Map数组来创建这个数组。
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
int[] updateCounts = simpleJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
batch);
return updateCounts;
}
// ... additional methods
}
3.6 Spring的数据库连接
l DataSourceUtils作为一个帮助类提供易用且强大的数据库访问能力,我们可以使用该类提供的静态方法从JNDI获取数据库连接以及在必要的时候关闭之。
l SmartDataSource是DataSource接口的一个扩展,用来提供数据库连接。
l AbstractDataSource是一个实现了DataSource接口的abstract基类。
l SingleConnectionDataSource是SmartDataSource接口的一个实现,其内部包装了一个单连接。该连接在使用之后将不会关闭,很显然它不能在多线程的环境下使用。
l DriverManagerDataSource类实现了SmartDataSource接口。可以使用bean properties来设置JDBC Driver属性,该类每次返回的都是一个新的连接。
l TransactionAwareDataSourceProxy作为目标DataSource的一个代理,在对目标DataSource包装的同时,还增加了Spring的事务管理能力。
l DataSourceTransactionManager类是PlatformTransactionManager接口的一个实现,用于处理单JDBC数据源。 它将从指定DataSource取得的JDBC连接绑定到当前线程,因此它也支持了每个数据源对应到一个线程。
l NativeJdbcExtractor如果你要访问特定厂商提供的与标准JDBC的API不同的JDBC对象,你可以配置一个包含NativeJdbcExtractor的JdbcTemplate或者OracleLobHandler。
3.7 使用SimpleJdbcInsert插入数据
SimpleJdbcInsert类的构建需要一个数据源,创建一个新的实例后,通过withTableName方法设置table名,也可以用usingColumns方法指定列名,下例构造了一个更新器并指定其更新表”t_actor”:
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
下例指定更新器只更新名称列和编号列:
insert = inser. usingColumns("name", "code");
可以通过三种方式给更新器指定值
l 使用Map实例:
Map<String, Object> paras = new HashMap<String, Object>();
paras.put("name", "name");
paras.put("code", "code");
insert.execute(paras);
l 使用MapSqlParameterSource:
SqlParameterSource paras = new MapSqlParameterSource()
.addValue("name", "name")
.addValue("code", "code");
insert.execute(paras);
l 使用BeanPropertySqlParameterSource:
private class UpdateBean
{
public UpdateBean(String name, String code)
{
this.name = name;
this.code = code;
}
public String getName()
{
return this.name;
}
public void setName(String name)
{
this.name = name;
}
public String getCode()
{
return this.code;
}
public void setCode(String code)
{
this.name = code;
}
private String name;
private String code;
}
UpdateBean updateBean = new UpdateBean("name", "code");
SqlParameterSource paras = new BeanPropertySqlParameterSource(updateBean);
insert.execute(paras);
3.8 使用SimpleJdbcCall调用存储过程
SimpleJdbcCall类的构建同样需要一个数据源,创建一个新的实例后,通过withProcedureName方法设置要调用的存储过程名,也可以用declareParameters方法定义调用参数(需要先调用withoutProcedureColumnMetaDataAccess方法指定数据库忽略所有的元数据处理并使用显示声明的参数。),下例构造了一个调用器并指定其调用过程"read_actor":
SimpleJdbcCall call = new SimpleJdbcCall(dataSource).withProcedureName("read_actor");
下例指定调用器定义了一组参数:
call = call.withProcedureName("read_actor")
. withoutProcedureColumnMetaDataAccess()
.declareParameters(
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
new SqlOutParameter("out_last_name", Types.VARCHAR),
new SqlOutParameter("out_birth_date", Types.DATE)
);
另外一种情况是,其中的某些参数值具有默认的返回值,我们需要在返回值中指定这些返回值。为了实现这个特性,我们可以使用useInParameterNames来指定一组需要被包含的参数名称。传递参数同样可以使用Map或者SqlParameterSource接口的实现类。同时,execute方法会返回一个Map类型的对象,里面是用返回参数名称索引的返回值的集合。
调用内置函数使用withFunctionName方法来完成。同时有一个叫做executeFunction的方法,将获得特定的Java类型的函数调用的返回值。另外有一个类似的便捷方法executeObject用于存储过程,但是他只能处理单个返回值的情况。
对于返回游标的情况,可以使用returningResultSet方法:
procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_all_actors")
.returningResultSet(
"actors",
ParameterizedBeanPropertyRowMapper.newInstance(Actor.class)
);
Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
return (List) m.get("actors");
3.9 MappingSqlQuery类
是一个可重用的查询抽象类,其具体类必须实现mapRow(ResultSet, int)抽象方法来将结果集中的每一行转换成Java对象。
private class CustomerMappingQuery extends MappingSqlQuery
{
public CustomerMappingQuery(DataSource ds) {
super(ds, "SELECT id, name FROM customer WHERE id = ?");
super.declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}
public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
Customer cust = new Customer();
cust.setId((Integer) rs.getObject("id"));
cust.setName(rs.getString("name"));
return cust;
}
}
3.10 SqlUpdate类
SqlUpdate类封装了一个可重复使用的SQL更新操作。该类提供了一系列update方法。 它是一个具体的类,通过在SQL语句中定义参数,不需要通过继承来实现定制。
public class UpdateCreditRating extends SqlUpdate
{
public UpdateCreditRating(DataSource ds)
{
setDataSource(ds);
setSql("update customer set credit_rating = ? where id = ?");
declareParameter(new SqlParameter(Types.NUMERIC));
declareParameter(new SqlParameter(Types.NUMERIC));
compile();
}
public int run(int id, int rating)
{
Object[] params =
new Object[] {
new Integer(rating),
new Integer(id)};
return update(params);
}
}
3.11 StoredProcedure类
StoredProcedure类是一个抽象基类,该类提供了多种protected的execute方法。从父类继承的sql属性用来指定RDBMS存储过程的名字。
private class MyStoredProcedure extends StoredProcedure
{
private static final String SQL = "sysdate";
public MyStoredProcedure(DataSource ds)
{
setDataSource(ds);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}
public Map execute()
{
return execute(new HashMap());
}
}
如果你需要给存储过程传输入参数, 则需要提供一个指定类型的execute(..)方法,该方法将调用基类的protected execute(Map parameters)方法。
3.12 SqlFunction类
SqlFunction类封装了一个SQL“函数”包装器(wrapper),该包装器适用于查询并返回一个单行结果集。SqlFunction是一个具体类,其用法是创建该类的实例,然后声明SQL语句以及参数就可以调用相关的run方法去多次执行函数。
public int countRows()
{
SqlFunction sf = new SqlFunction(
dataSource,
"select count(*) from mytable"
);
sf.compile();
return sf.run();
}
相关文章推荐
- Spring 学习笔记(四)——数据访问与事务
- Spring源码学习笔记---数据访问(二)
- 秋天里的春天-spring笔记(一)
- 秋天里的春天-spring笔记(二):IoC
- 微软企业库5.0学习笔记(三十五)数据访问模块 DataSet以及数据库事务
- 秋天里的春天-spring笔记(三):AOP in Spring
- Spring Boot 学习笔记4——结合Mybatis访问数据库及事务控制
- 微软企业库5.0学习笔记(三十五)数据访问模块 DataSet以及数据库事务
- 【Spring】官网教程阅读笔记(三):Spring中使用JDBC访问关系数据
- Spring 4 官方文档学习(九)数据访问之事务管理
- (笔记)Spring实战_征服数据库(1)_Spring的数据访问哲学
- Spring 数据访问之事务管理
- 微软企业库5.0学习笔记(三十五)数据访问模块 DataSet以及数据库事务
- Spring源码学习笔记---数据访问(一)
- Spring数据访问和事务
- Spring事务-注解--实体类|数据访问类|业务类
- SpringBoot学习笔记 - 数据访问(Spring Data JPA)
- Spring 框架参考文档(四)-数据访问之(事务管理)
- Beginning Spring学习笔记——第4章(二)使用Spring执行数据访问操作
- spring.net 框架分析(六)数据访问