Spring+JDBC的簡單配置和開發
目前很多的公司採用了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>