目前很多的公司采用了spring+jdbc的配置开发项目,下面介绍怎么配置环境到开发时候的注意事项:
①引入必要的jar文件
JDBC驱动(mysql为例):
mysql-connector-5.1.7.jar
数据库连接池(dbcp为例):
commons-dbcp.jar
commons-pool.jar
spring核心必须包:
spring.jar
commons-logging.jar
AOP非必须(方便使用建议添加):
cglib-nodep-2.1_3.jar
aspectjrt.jar
注解(一般都会使用):
common-annotations.jar
aspectjweaver.jar
一、配置数据源dataSource:
学过了spring后,这里采用spring容器管理来实例化数据源,一般在配置之前都会开启事务管理的命名空间,一般都会使用事务管理。
在配置一个工程的时候一定要注意的是循序渐进,不要一口气全部配置好所有的东西,这样如果经验不丰富的朋友,可能难以解决现有的报错。所以在配置好数据源的时候测试一下这个数据源是否正确,如果经验比较丰富的可以多配置几步再测试。
<?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-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!-- 开启注解 -->
<context:annotation-config/>
<!-- 配置数据连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql:///test?useUnicode=true&characterEncoding=UTF-8"></property>
<property name="driverClassName" value="org.gjt.mm.mysql.Driver"></property>
<property name="username" value="root"></property>
<property name="password" value="heyingxxx"></property>
<!-- 连接池初始值 -->
<property name="initialSize" value="2"/>
<!-- 连接池最大值 -->
<property name="maxActive" value="100"/>
<!-- 连接池最大空闲值 -->
<property name="maxIdle" value="5"/>
<!-- 连接池最小空闲值 -->
<property name="minIdle" value="2"/>
</bean>
</beans>
这个dataSource已经交给spring容器管理了,现在使用spring实例化这个bean,以前介绍过方法:
public void testSave() throws Exception {
try {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// PersonService personService = (PersonService) context.getBean("personService");
DataSource dataSource = (DataSource) context.getBean("dataSource");
// personService.save(new Person("heying", 2));
System.out.println(dataSource.getConnection());
context.close();
} catch (Exception e) {
e.printStackTrace();
}
}
二、配置业务bean,这边模拟一个员工的录入,简化操作,user只有两个属性,id和name
Person.java:
package com.heying.bean;
public class Person {
private String name;
private Integer id;
// 初始化person
public Person(String name, Integer id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
PersonService接口:
package com.heying.service;
import java.util.List;
import com.heying.bean.Person;
public interface PersonService {
public void save(Person person);
public void update(Integer id,Person person);
public List<Person> findAll();
}
PersonService接口的实现类:
业务层面需要调用到数据库,所以需要将dataSource注入到PersonServiceBean,在dataSource无法直接实现数据的增删改查,如果这里只是单单的把数据库注入到PersonServiceBean显得有些不足,需要通过getConnection等大量的重复动作, 所以在JdbcTemplate中有很多现有的方法,这样就需要在使用PersonServiceBean的时候不仅需要注入dataSource,还需要初始化JdbcTemplate,这边可以使用两个或者多个方式。第一种是前面介绍的在业务bean->PersonServiceBean初始化时候注入dataSource时候采用setter方式,在setter中new JdbcTemplate(dataSource),还有一种就是在实例化业务bean->PersonServiceBean的时候使用init方法,值得注意的是,一定在注入了dataSource之后初始化JdbcTemplate,否则dataSource为null将报错,因为JdbcTemplate需要数据源初始化。这边采用init初始化:
package com.heying.service.impl;
import java.util.List;
import javax.annotation.Resource;
import javax.sql.DataSource;
import com.heying.bean.Person;
import com.heying.service.PersonService;
public class PersonServiceBean implements PersonService{
@Resource
private DataSource dataSource; //注入dataSource到PersonServiceBean
private JdbcTemplate jdbcTemplate ; // 可以使用init方法初始化,也可以在bean.xml中配置使用setter方法注入PersonServiceBean时候初始化
public void init(){
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void save(Person person) {
try {
System.out.println(dataSource.getConnection());
System.out.println(jdbcTemplate);
jdbcTemplate.update("insert into t_user (name,id) values (?,?) ",
new Object[]{person.getName(),person.getId()},
new int[]{Types.VARCHAR,Types.INTEGER});
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void update(Integer id, Person person) {
}
@Override
public List<Person> findAll() {
return null;
}
}
bean.xml文件:
<bean id="personService" class="com.heying.service.impl.PersonServiceBean" init-method="init"></bean>
测试:
@Test
public void testSave() throws Exception {
try {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
PersonService personService = (PersonService) context.getBean("personService");
personService.save(new Person("heying51123", 1102));
context.close();
} catch (Exception e) {
e.printStackTrace();
}
}
结果:
数据库:
基于注解的事务管理器:
这样就能完成了一个完整的过程,但是在企业开发中还必须使用事务管理,所以,还必须配置事务管理,防止异常发生时同一个事务中所有的操作回滚,这里注意的是在spring中的事务管理,默认只回滚RuntimeException()的异常,这类异常不能通过捕获处理,所以就不需要捕获的异常是特殊的Exception的子类(unchecked Exception),而其他的异常也是Exception的子类,这些属于检查异常,默认是不回滚事务的,所以需要在回滚的方法上定义 @Transactional(rollbackFor=Exception.class):
bean.xml文件:
事务管理的命名空间已经引入,要想使事务交给spring管理,需要和注解一样需要注册事务管理器,初始化时就需要将dataSource注入到txManager,交给事务管理器管理
<!-- 注册事务管理器 -->
<tx:annotation-driven transaction-manager="txManager"/>
<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
第一次使用insert插入重复主键看看数据库是否回滚:
save方法:
@Override
public void save(Person person) throws Exception{
try {
System.out.println(dataSource.getConnection());
System.out.println(jdbcTemplate);
int updateRows = jdbcTemplate.update("insert into t_user (name,id) values (?,?) ",
new Object[]{person.getName(),person.getId()},
new int[]{Types.VARCHAR,Types.INTEGER});
int updateRows2 = jdbcTemplate.update("insert into t_user (name,id) values (?,?) ",
new Object[]{person.getName(),person.getId()},
new int[]{Types.VARCHAR,Types.INTEGER});
System.out.println("insert影响了: "+updateRows+ " ROWS");
} catch (Exception e) {
throw new Exception("普通捕获异常");
}
}
测试:
public class TestCase {
private static AbstractApplicationContext context;
private static PersonService personService;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
context = new ClassPathXmlApplicationContext("bean.xml");
personService = (PersonService) context.getBean("personService");
}
@Test
public void testSave() throws Exception {
try {
personService.save(new Person("heying110", 1102));
} catch (Exception e) {
System.err.println("********异常********* "+e.getMessage());
}finally{
List<Person> list = personService.findAll();
for (int i = 0; i < list.size(); i++) {
System.out.println("##-->> "+list.get(i).getName());
System.out.println("##-->> "+list.get(i).getId());
}
context.close();
}
}
}
结果:
总结:
显然第二条数据检查异常并没有影响第一条数据的插入
第二次加上@Transactional(rollbackFor=Exception.class)定义需要回滚的异常:
@Transactional(rollbackFor=Exception.class)
public void save(Person person) {
fun()...
}
测试:
personService.save(new Person("heying110", 1103));
结果:
1103没有插入,第二条记录出现异常,第一条就回滚了,一般数据库访问出现的异常比较基层,一般不可能通过try语句快解决的,这样也有的喜欢抛出一个运行期异常,不用指明@Transactional(rollbackFor=Exception.class)也能回滚所有出现异常的操作。
不是所有的访问都需要事务的,比如查询操作可以不适用事务管理,使用事务管理,必定会影响性能,所以介绍几种常见的事务传播属性(Propagation )的方式:
REQUIRES
(默认):加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
PROPAGATION_SUPPORTS
:支持当前事务,如果当前没有事务,就以非事务方式执行,跟随调用的方法是否有事务决定。
PROPAGATION_MANDATORY
:要求在一个已有的事务中执行,业务方法不能发起自己的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW
:不管业务没有没有事务,总会开启新事务,如果调用方法中存在事务,把当前事务挂起,新的事务会创建,直到方法结束后,新事务才算结束,原先挂起的事务恢复执行。
PROPAGATION_NOT_SUPPORTED
:以非事务方式执行操作,如果被另外一个方法调用,且当前存在事务,就把当前事务挂起,调用方法结束后,原先事务在继续执行。
PROPAGATION_NEVER
:要求在一个没有事务方式中执行,如果存在事务,则抛出异常,和MANDATORY相反。
NESTED
:如果一个活动的事务存在,则运行在一个嵌套的事务中,如果没有活动的事务,则按照REQUIRES属性执行,它使用一个单独的事务,这个事务拥有多个回滚保存点,内部事务不会对外部事务产生影响,它只对
DataSourceTransactionManager
事务管理起效。
Spring事务隔离级别:
1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应
2. ISOLATION_READ_UNCOMMITTED(读未提交): 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据,这种隔离级别会产生脏读,不可重复读和幻像读。
3. ISOLATION_READ_COMMITTED:(读已提交) 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
4. ISOLATION_REPEATABLE_READ(可重复读): 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读 ,它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生。
5. ISOLATION_SERIALIZABLE:(串行化) 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行, 除了防止脏读,不可重复读外,还避免了幻像读。
mysql数据隔离级别藏参考:
介绍一下基于XML方式的事务管理,采用了AOP中的通知管理将事务交给spring管理
<aop:config>
<!-- 定义一个切面,拦截的包以及子包类任意类任意方法 -->
<aop:pointcut id="transactionPointcut" expression="execution(* com.heying.service.PersonService..*.*(..))"/>
<aop:advisor advice-ref="txtAdvice" pointcut-ref="transactionPointcut"/>
</aop:config>
<tx:advice id="txtAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" propagation="NOT_SUPPORTED"/> <!-- get开头方法不使用 -->
<tx:method name="*"/><!-- 其他方法,默认 propagation="REQUIRED" -->
</tx:attributes>
</tx:advice>