本篇随笔是上两篇的延续:三种数据库访问——原生JDBC;数据库连接池:Druid
Spring主要提供JDBC模板方式、关系数据库对象化方式、SimpleJdbc方式、事务管理来简化JDBC编程
Spring提供了3个模板类:
JdbcTemplate主要提供以下4类方法:
接下来,通过一个示例项目来展示如何使用Spring的JDBC框架访问数据库。假设该项目的功能有:保存用户信息、查询用户信息。
CREATE TABLE `user` ( `id` int(10) NOT NULL auto_increment, `name` varchar(30) default NULL, `age` int(3) default NULL, PRIMARY KEY (`id`) )
依赖配置:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.26</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>0.2.25</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>3.2.4.RELEASE</version> </dependency>
依赖的包有:Junit、mysql驱动器、druid(阿里巴巴开发的高性能的数据库连接池)、spring-context、spring-jdbc
public class User implements Serializable{ private Long id; private String name; private Integer age; //setter getter 略 public String toString() { return "User [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
package edu.shao.springJdbc.dao; import java.util.List; import edu.shao.springJdbc.po.User; public interface IUserDao { public void save(User user); public List<User> query(String sql,Object[] args); }
package edu.shao.springJdbc.dao.impl; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.support.JdbcDaoSupport; import edu.shao.springJdbc.dao.IUserDao; import edu.shao.springJdbc.po.User; public class UserDaoImpl extends JdbcDaoSupport implements IUserDao { class UserRowMapper implements RowMapper<User> { //实现ResultSet到User实体的转换 public User mapRow(ResultSet rs, int rowNum) throws SQLException { User m = new User(); m.setId(rs.getLong("id")); m.setName(rs.getString("name")); m.setAge(rs.getInt("age")); return m; } }; public void save(User model) { getJdbcTemplate().update("insert into user(name,age) values(?,?)", model.getName(), model.getAge()); } public List<User> query(String sql, Object[] args) { return getJdbcTemplate().query(sql, args, new UserRowMapper()); } }
package edu.shao.springJdbc.service; public interface IUserService { void saveUser(); void saveUserThrowException() throws Exception; void findUsers(); }
package edu.shao.springJdbc.service.impl; import java.util.List; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import edu.shao.springJdbc.dao.IUserDao; import edu.shao.springJdbc.po.User; import edu.shao.springJdbc.service.IUserService; @Transactional public class UserServiceImpl implements IUserService { private IUserDao userDao; public void saveUser() { User u1=new User(); u1.setName("邵"); u1.setAge(24); userDao.save(u1); if(1+1>1){ throw new RuntimeException("Runtime error...");//抛出运行时异常:RuntimeException } User u2=new User(); u2.setName("陈"); u2.setAge(20); userDao.save(u2); } public void saveUserThrowException() throws Exception { User u1=new User(); u1.setName("邵"); u1.setAge(24); userDao.save(u1); if(1+1>1){ throw new Exception("Runtime error...");//抛出一般的异常:Exception } User u2=new User(); u2.setName("陈"); u2.setAge(20); userDao.save(u2); } @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) public void findUsers() { List<User> users=userDao.query("select * from user where age>?", new Object[]{17}); for (User user : users) { System.out.println(user); } } //setter getter略 }
Spring对事务的管理有丰富的支持,Spring提供了编程式配置事务和声明式配置事务,其中,声明式事务有以下两种方式 :
(编程式的事务处理有些侵入性。通常我们的事务需求并没有要求在事务的边界上进行如此精确的控制,我们一般采用"声明式事务"。)
上面我们采用了基于注解的方式来配置事务。
applicationContext-dataSource.xml
<?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" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="initialSize" value="1" /> <property name="maxActive" value="20" /> </bean> </beans>
applicationContext-jdbc.xml
<?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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <import resource="applicationContext-dataSource.xml" /> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 首先定义抽象的abstractDao,其有一个jdbcTemplate属性,从而可以让继承的子类自动继承jdbcTemplate属性注入; --> <bean id="abstractDao" abstract="true"> <property name="jdbcTemplate" ref="jdbcTemplate" /> </bean> <bean id="userDao" class="edu.shao.springJdbc.dao.impl.UserDaoImpl" parent="abstractDao" /> <bean id="userService" class="edu.shao.springJdbc.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:annotation-driven transaction-manager="txManager" /> </beans>
上面<tx:annotation-driven transaction-manager="txManager" /> 这句话的作用是注册事务处理器。
我们只需要在类上加上注解@Transactional,就可以指定这个类需要受Spring的事务管理。默认Spring为每个方法开启一个事务,如果方法发生运行期异常(RuntimeException),事务会进行回滚;如果发生一般的异常(Exception),事务不进行回滚。
package edu.shao.springJdbc; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import edu.shao.springJdbc.service.IUserService; public class SpringJdbcTest { private static ApplicationContext ctx = null; @BeforeClass //表示在所以测试方法之前执行,且只执行一次。 public static void onlyOnce() { ctx = new ClassPathXmlApplicationContext("db/applicationContext-jdbc.xml"); } @Test public void testSave(){ IUserService service=ctx.getBean("userService",IUserService.class); service.saveUser(); } @Test public void testSaveThrowException() throws Exception{ IUserService service=ctx.getBean("userService",IUserService.class); service.saveUserThrowException(); } @Test public void testJDBCDaoQuery(){ IUserService service=ctx.getBean("userService",IUserService.class); service.findUsers(); } }
运行测试类,
第一个测试方法,后台输出异常:java.lang.RuntimeException,查看数据库发现数据没有插入,说明事务进行了回滚。
第二个测试方法,后台输出异常:java.lang.Exception ,查看数据库发现第一条数据插入,而第二条数据没有插入,说明事务没有进行了回滚。
说明了Spring的事务支持默认只对运行期异常(RuntimeException)进行回滚,如果执行sql操作的时候会发生sql异常,不属于运行期异常,那Spring是怎么进行事务回滚的呢 ?
Spring把SQLException等异常转化为了DataAccessException,后者是一种RuntimeException,所以只对RuntimeException异常进行回滚是很合理的。
其他注解方式:
关于事务的传播属性有下面几种配置:
总结:
事务是企业应用开发的重要组成部分,它使软件更加可靠。它们确保一种要么全有 要么全无的行为,防止数据不一致而导致的不可预测的错误发生。 事务同时也支持并发,防止并发应用线程在操作同一数据时互相影响。
以前我们写Jdbc代码的时候,可能需要自己手动去开启事务,然后方法执行结束之后再去提交事务,全部都嵌套在我们的业务代码之中,具有很强的侵入性....
使用Spring提供事务管理机制,我们只需要配置XML或使用Annotion进行注解就可以实现事务的管理和配置,减少了代码之间的耦合,配置也很方便,很大程度上提升了我们的开发效率。