Spring JDBC
Spring模板和回调机制
在直接使用JDBC时。我们需要处理获取连接,处理异常、释放资源等整个流程。Spring为支持的持久化技术提供了模板访问的方式,我们只需要提供具体的操作数据代码即可,可以大幅度提高开发效率。
Spring将相同的数据访问流程固定到模板类中,例如,获取连接、释放资源等等。将数据库操作中固定和变化的部分分开,同时保证模板类线程安全,以便多个线程共享同一模块实例。固定的部分在模板类中已经写好,变化的部分通过回调接口开放出来,用于具体的数据访问和结果处理操作。
Spring为很多支持的持久化技术都提供了简化操作的模板和回调。例如为传统的JDBC提供了JdbcTemplate模块类,为Hibernate提供了HibernateTemplate模板类等等很多。我们直接使用即可。
Spring JDBC入门例子
不论使用任何持久化技术,都必须拥有数据库连接。在Spring中,我们需要配置数据库连接池来获取连接。我们的代码中都统一使用c3p0连接池。
1、首先我们要先导包,我们需要导入除了 4+2,还需要jdbc包和tx包(事务操作),然后还需要导入c3p0包和数据库驱动等
2、创建一个简单数据库和数据表
CREATE DATABASE account;
USE springdb;
CREATE TABLE t_account(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
money VARCHAR(50)
);
INSERT INTO t_account(name,money) VALUES('zhangsan','1000');
//我们来演示一下怎么使用JdbcTemplate
public class TestJdbc {
@Test
public void test() throws Exception{
//创建c3p0连接池,并设置相应的数据库参数
ComboPooledDataSource datasource=new ComboPooledDataSource();
datasource.setDriverClass("com.mysql.jdbc.Driver");
datasource.setJdbcUrl("jdbc:mysql:///account");
datasource.setUser("root");
datasource.setPassword("root");
//创建JdbcTemplate对数据库进行操作
JdbcTemplate template=new JdbcTemplate(datasource);
String sql="insert into t_account(name,money) values(?,?)";
template.update(sql, "lisi","1000");
}
}
结果如下所示:
详解JdbcTemplate
JdbcTemplate与DBUtils中的QueryRunner非常相似。QueryRunner几乎可以完成任何数据访问的操作,并且非常方便简洁。Spring也提供了一套与QueryRunner功能类似的,叫做JdbcTemplate。
我们上面的例子都是手动通过API 来创建连接池,来创建JdbcTemplate,我们完全可以使用我们学习的IoC来使用配置文件来替代那些操作,这也是我们所提倡的。
使用配置文件
同样,要导入上面例子的jar包,不再重复叙述
我们在Dao中使用Jdbctemplate,JdbcTemplate直接在配置文件中配置好,
直接在Dao中注入即可。
//我们先创建一个实体类
public class Account {
private int id;
private String name;
private String money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMoney() {
return money;
}
public void setMoney(String money) {
this.money = money;
}
@Override
public String toString() {
return "Account [id=" + id + ", name=" + name + ", money=" + money + "]";
}
}
//创建Dao来对数据进行操作
public class AccountDao {
//提供JdbcTemplate和set方法来进行注入
private JdbcTemplate template;
public void setTemplate(JdbcTemplate template) {
this.template = template;
}
//更新方法
public void update(Account account){
String sql="update t_account set name=?,money=? where id=?";
Object []params={account.getName(),account.getMoney(),account.getId()};
template.update(sql, params);
}
}
<!--编写配置文件-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置连接池-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///account"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--实例JdbcTemplate,并且通过dataSource属性注入连接池-->
<bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--实例化Dao,并注入JdbcTemplate-->
<bean name="userdao" class="com.pngyul.dao.AccountDao">
<property name="template" ref="jdbctemplate"></property>
</bean>
</beans>
//我们来测试一下
public class TestJdbc {
@Test
public void test() throws Exception{
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
AccountDao accountdao=(AccountDao ) ac.getBean("accountrdao");
Account account=new Account ();
account.setId(1);
account.setName("marry");
account.setMoney("1200");
accountdao.update(account);
}
}
查询返回记录
// 查询一条记录
public void query1(){
String sql = "select * from t_account where id = ?";
Account account = jdbcTemplate.query(sql, new RowMapper<Account>(){
@Override
public User mapRow(ResultSet rs, int arg1) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getDouble("money"));
return account;
}}, 1);
System.out.println(account);
}
//返回查询个数
public int getTotalCount() {
String sql = "select count(*) from t_user ";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
// 查询所有
public List<Account> getAll() {
String sql = "select * from t_account";
List<Account> list = jdbcTemplate.query(sql, new RowMapper<Account>(){
@Override
public User mapRow(ResultSet rs, int arg1) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getDouble("money"));
return account;
}});
return list;
}
ps:更新操作直接调用jdbcTemplate.update(..)即可!
使用属性文件配置数据库信息
数据库的配置信息有可能需要经常改动,所以一般将数据库配置信息放在属性文件中。
//创建一个属性文件,名为jdbc.properties
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/account
user=root
password=root
<!--引入属性文件,以${xxx}的方式引入属性-->
<context:property-placeholder location="jdbc.properties"/>
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
ps:需要导入 context 约束
JdbcDaoSupport
我们的Dao层使用JdbcTemplate都需要提供一个JdbcTemplate属性和set方法来进行注入,而Spring为我们提供了一个父类JdbcDaoSupport,已经帮我们做好了这些操作。
//我们的Dao继承JdbcDaoSupport
public class UserDao extends JdbcDaoSupport{
public void update(Account account){
String sql="update t_account set name=?,money=? where id=?";
Object []params={account.getName(),account.getMoney(),account.getId()};
this.getJdbcTemplate().update(sql, params);
}
}
//更改配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/account"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--只需要将连接池给它,父类会自动帮我们创建JdbcTemplate-->
<bean name="accountdao" class="com.pngyul.dao.AccountDao">
<property name="dataSource" ref="datasource"></property>
</bean>
</beans>
Spring的事务操作
实际开发中,Spring的事务管理基本上是被使用最多的功能,Spring的提供了灵活方便的事务管理功能。
事务的基础知识和可能遇到的数据并发问题还有数据隔离级别等知识已经学习过,可以从以前的博文中进行学习,这里就不再进行叙述
事务传播行为
当我们进行开发时,事务一般都是设置在Service层,如果我们调用基于Spring的service方法时,它将运行于Spring管理的事务环境中,如果该Service方法中中除了调用Dao层的方法,在内部还调用了其他的Service方法来共同完成一个完整的业务操作,事务传播行为就是控制当前的事务如何传播到被调用的其他Service方法中。
PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
Spring的编程式的事务管理
实际应用中,很少需要通过编程来进行事务管理。Spring为编程式事务管理提供了模板类TransactionTemplate,TransactionTemplate是线程安全的,因此我们可以在业务类中共享TransactionTemplate进行事务管理。
TracsactionTemplate有两个主要的方法
void setTransactionManager(PlatformTransactionManager transactionManager):设置事务管理器
Object execute(TransactionCallback action):在TransactionCallback回调接口中定义需要以事务方式组织的数据访问逻辑。简单的说需要以事务方式处理的代码放在这里面。
TransactionCallback接口中只有一个方法 :Object doInTransaction(TransactionStatus status).如果操作不会返回任何结果,可以使用TransactionCallback的子接口TransactionCallbackWithoutResult.
注意:在spring中玩事务管理.最为核心的对象就是TransactionManager对象
//创建一个accountdao,应该有两个方法,一个是给收款人加钱,一个给付款人减钱
public class AccountDao extends JdbcDaoSupport {
public void increaseMoney(Integer id, Double money) {
getJdbcTemplate().update("update t_account set money = money+? where id = ? ", money,id);
}
public void decreaseMoney(Integer id, Double money) {
getJdbcTemplate().update("update t_account set money = money-? where id = ? ", money,id);
}
}
//在Service层使用编程式事务进行转账操作
public class AccountService{
private AccountDao ad;
private TransactionTemplate tt;
//转账操作
@Override
public void transfer(Integer from, Integer to, Double money) {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
// TODO Auto-generated method stub
//先减钱
ad.decreaseMoney(from, money);
//再加钱
ad.increaseMoney(to, money);
}
});
}
public void setAd(AccountDao ad) {
this.ad = ad;
}
public void setTt(TransactionTemplate tt) {
this.tt = tt;
}
}
<!--编写配置文件-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--加载数据库配置文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--设置连接池配置-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--实例TransactionTemplate模板类,需要一个事务管理器-->
<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!--实例事务管理器,需要一个数据源-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--实例bankDao,需要数据源-->
<bean id="accountDao" class="com.pngyul.dao.AccountDao">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--实例bankService,需要注入两个参数-->
<bean id="accountService" class="com.pngyul.service.AccountService">
<property name="tt" ref="transactionTemplate"></property>
<property name="ad" ref="accountDao"></property>
</bean>
</beans>
//测试一下
public class TestDemo {
@Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService service=(AccountService) ac.getBean("accountService");
service.transfer(1, 2, 200);
}
}
基于 tx/aop 命名空间的配置
Spring在XML配置中,提供了tx命名空间,以明确的结构方式定义事务相关信息,配置aop提供的切面定义,事务配置得到了大大的简化。这也是开发中最常用的事务管理的方法
//我们还使用前面转账的例子,稍微修该一下AccountService类
public class AccountService {
private AccountDao ad;
public void setAd(AccountDao ad) {
this.ad = ad;
}
public void transfer(Integer from, Integer to, Double money) {
//先减钱
ad.decreaseMoney(from, money);
//再加钱
ad.increaseMoney(to, money);
}
}
<!--
重新写配置文件
注意的是配置文件需要引入tx和aop命名空间
-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">
<!-- 指定spring读取db.properies配置 -->
<context:component-scan base-package="com.pngyul.txDemo"></context:component-scan>
<context:property-placeholder location="classpath:db.properties"/>
<!-- 事务核心管理器,封装了所有的事务操作,依赖于连接池 -->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务模板对象 -->
<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!-- 事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager" >
<tx:attributes>
<!-- 以方法为单位,指定方法应用什么事务属性
isolation:隔离级别
propagation:传播行为
read-only:是否只读
-->
<tx:method name="find*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
</tx:attributes>
</tx:advice>
<!-- 织入通知 -->
<aop:config>
<!-- 配置切点表达式-->
<aop:pointcut expression="execution(* com.pngyul.service.*Service.*(..))" id="txPc"/>
<!-- 配置切面 : 通知+切点
advice-ref:通知的名称
pointcut-ref:切点的名称
-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPc" />
</aop:config>
<!-- 1.连接池 -->
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="jdbcUrl" value="${jdbc.jdbcUrl}" ></property>
<property name="driverClass" value="${jdbc.driverClass}" ></property>
<property name="user" value="${jdbc.user}" ></property>
<property name="password" value="${jdbc.password}" ></property>
</bean>
<!-- 2.dao -->
<bean name="accountDao" class="com.pngyul.dao.AccountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 3.service -->
<bean name="accountService" class="com.pngyul.service.AccountService">
<property name="ad" ref="accountDao"></property>
</bean>
</beans>
//进行测试
public class TestDemo {
@Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
BankService service=(BankService) ac.getBean("proxyBankService");
service.transfer(1, 2, 200);
}
}
使用注解配置声明式事务
除了基于XML的事务配置之外,Spring还提供了基于注解的事务配置,使用@Transactional对需要使用注解的类或方法进行标注,在容器中配置基于注解的事务增强驱动,即可启用基于注解的声明式事务。
//对整个类使用注解,也可以对单独的方法使用注解
//一般来说,我们只需要使用默认的事务属性即可,如果需要,直接使用以下格式
//@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT)
public class AccountService {
private AccountDao ad;
public void setAd(AccountDao ad) {
this.ad = ad;
}
public void transfer(Integer from, Integer to, Double money) {
//先减钱
ad.decreaseMoney(from, money);
//再加钱
ad.increaseMoney(to, money);
}
}
//注解写在类上,表示对整个类的所有方法都设置事务管理。再次基础上,如果要对某个方法设置唯一的事务属性,只要在指定的方法上再写一遍注解即可。(类似于覆盖的意思)
//注解写在方法上,表示对该方法设置事务管理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<bean id="AccountDao" class="com.pngyul.dao.AccountDao">
<property name="dataSource" ref="datasource"></property>
</bean>
<bean name="accountService" class="com.pngyul.service.AccountService">
<property name="ad" ref="accountDao"></property>
</bean>
<!--注解驱动,对添加@Transactional注解的Bean织入事务管理,peoxy-target-class属性如果为true,使用CGLib-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>