1.開發前準備
1.1 原型(靜態頁面)導入
idea和eclipse中目錄結構是不同,idea中的web目錄和eclipse中webRoot是一個意思
1.2 數據庫生成
使用mysql數據庫(sql文本在資料中)
2.搭建項目環境
2.1 引入ssh的jar和相關配置
(1)在WEB-INF中創建lib文件夾,並複製jar包
(2)設置依賴此lib包
2.1 Web.xml以及其他配置文件
(1)web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name></display-name>
<!-- Spring ServletContextListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 加載配置文件默認 WEB-INF/applicationContext.xml -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- Struts2 核心 過濾器 -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
- ContextLoaderListener:
在ServletContext初始化的時候,加載spring核心配置(applicationContext.xml),因爲默認加載的位置是WEB-INF/applicationContext.xml,所以需要重新設置全局配置 - StrutsPrepareAndExecuteFilter:
Struts2核心過濾器,入口
在項目下創建resources文件夾,並設置爲resources(等於是一個權限),在resources中新建我們需要的配置文件
(2)log4j.properties文件
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=info, stdout
(3)struts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<!--
開發者模式:關閉:副作用:如果是ajax請求的情況下,一旦出錯,不會在頁面上打印錯誤
所以採用struts.i18n.reload和struts.configuration.xml.reload的模式
-->
<!-- <constant name="struts.devMode" value="true" /> -->
<!-- 不用重啓服務器 -->
<constant name="struts.configuration.xml.reload" value="true" />
<!-- 表單樣式 -->
<constant name="struts.ui.theme" value="simple" />
<package name="default" namespace="/" extends="struts-default">
</package>
</struts>
(4)applicationContext.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:cache="http://www.springframework.org/schema/cache"
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
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- 引入外部屬性文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- 連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- sessionFactory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<!-- 第一部分: 連接池 -->
<property name="dataSource" ref="dataSource" />
<!-- 第二部分: hibernate常用屬性 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
<!-- 第三部分: 引入hbm -->
<!-- <property name="mappingResources">
<list>
<value>cn/itcast/storemanager/domain/Goods.hbm.xml</value>
<value>cn/itcast/storemanager/domain/History.hbm.xml</value>
<value>cn/itcast/storemanager/domain/Store.hbm.xml</value>
<value>cn/itcast/storemanager/domain/Userinfo.hbm.xml</value>
</list>
</property> -->
<!-- 可以批量引入hbm
支持通配符
-->
<property name="mappingLocations">
<list>
<value>classpath:cn/itcast/storemanager/domain/*.hbm.xml</value>
</list>
</property>
</bean>
<!-- 配置事務 -->
<!-- 配置事務管理器平臺 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 事務管理通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" read-only="false"/>
<tx:method name="update*" read-only="false"/>
<tx:method name="delete*" read-only="false"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 切入點和切面 -->
<aop:config>
<aop:pointcut expression="bean(*Service)" id="txPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
擴展1:ComboPooledDataSource(c3p0連接池配置)
spring內置了一個數據源DriverManagerDataSource
,在spring-jdbc-4.2.4.RELEASE.jar
包下
<!-- 配置內置的數據源bean -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///itcastspring"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- jdbctemplate對象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入數據源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
但這個數據源不建議生成環境下使用,我們使用c3p0的數據源ComboPooledDataSource
,在c3p0-0.9.2.1.jar
下
<!-- c3p0連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///itcastspring"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- jdbctemplate對象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入數據源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
這裏的JdbcTemplate是一個模板工具類,簡化jdbc編程(詳情可參考spring第三天 spring jdbcTemplate的使用)
我們這裏可以使用Spring的junit測試
(可以參考Spring第二天 spring的junit測試)
作用:可以簡化測試代碼,直接讀取spring的核心配置,手動初始化spring容器
通過兩個註解:
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(locations=“classpath:applicationContext.xml”)
其中@RunWith是junit-4.12.jar
包下的,其他SpringJUnit4ClassRunner和@ContextConfiguration是spring-test-4.2.4.RELEASE.jar
包下
//目標:測試一下spring的bean的某些功能
@RunWith(SpringJUnit4ClassRunner.class) //junit整合spring的測試//立馬開啓了spring的註解
@ContextConfiguration(locations="classpath:applicationContext.xml") //加載核心配置文件,自動構建spring容器
public class SpringTest {
//注入要測試bean
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testCreatetable(){
jdbcTemplate.execute("create table test004(id int,name varchar(20))");
}
}
但是我們這邊不使用JdbcTemplate
擴展2:LocalSessionFactoryBean
spring爲不同的ORM持久化技術,提供了不同的支持
ORM持久化技術 | 模板類 |
---|---|
JDBC | org.springframework.jdbc.core.JdbcTemplate |
Hibernate5.0 | org.springframework.orm.hibernate5.HibernateTemplate |
IBatis(MyBatis) | org.springframework.orm.ibatis.SqlMapClientTemplate |
JPA | org.springframework.orm.jpa.JpaTemplate |
因爲我們已經學了Hibernate,所以我們這邊使用spring對Hibernate的支持
Hibernate的核心是SessionFactory
,並由SessionFactory創建Session,再由Session操作數據庫。
public class HibernateUtil {
private static SessionFactory factory;
// 使用靜態代碼塊獲取SessionFactory
static {
//細節:Hibernate把所有可預見的異常,都轉成了運行時異常(工具中不會提示要添加異常塊)
try {
// 加載hibernate.cfg.xml配置文件
Configuration config = new Configuration().configure();
factory = config.buildSessionFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 獲取一個新的Session對象(每次都是一個新對象)
* @return
*/
public static Session openSession(){
return factory.openSession();
}
/**
* 獲取一個新的Session對象(每次都是一個新對象)
* @return
*/
public static Session getCurrentSession(){
return factory.getCurrentSession();
}
}
我們現在可以通過spring-orm
包提供LocalSessionFactoryBean
實現
spring整合Hibernate的方式有兩種
(1)方式一:直接在spring中加載hibernate配置文件 (零障礙整合)
<!-- spring整合Hibernate:將session工廠交給spring管理,這是spring整合hibernate的核心 -->
<!-- 方式一: 直接在spring中加載hibernate配置文件 (零障礙整合) -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- 注入Hibernate的核心配置文件
提示技巧:xxxLocation,一般都要加classpath -->
<property name="configLocation" value="classpath:hibernate.cfg.xml"/>
</bean>
原理:Web.xml 配置監聽器 —> 初始化spring容器 —> 初始化hibernate配置—>初始化所有單例對象 —> sessionFactory 建表
容器之間的關係原理:
(2)方式一:(推薦)將hibernate參數配置到spring文件
沒有hibernate.cfg.xml配置文件 !!!完全由spring進行管理
<!-- 方式二: (推薦)將hibernate參數配置到spring文件 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<!-- 1.數據源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 2.Hibernate的屬性 -->
<property name="hibernateProperties">
<props>
<!-- 設置方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<!-- 設置打印sql語句 -->
<prop key="hibernate.show_sql">true</prop>
<!-- 格式化sql語句 -->
<prop key="hibernate.format_sql">true</prop>
<!-- 自動建表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
<!-- 3.mapping映射 -->
<property name="mappingResources">
<list>
<value>cn/itcast/ssh/domain/Book.hbm.xml</value>
</list>
</property>
</bean>
測試代碼
//目標:測試一下spring的bean的某些功能
@RunWith(SpringJUnit4ClassRunner.class) //junit整合spring的測試//立馬開啓了spring的註解
@ContextConfiguration(locations="classpath:applicationContext.xml") //加載核心配置文件,自動構建spring容器
public class SpringTest {
@Autowired
private SessionFactory sessionFactory;
@Test
public void testCreatetable(){
Session session = sessionFactory.openSession();
System.out.println(session);
//開啓事務
Transaction tx =session.beginTransaction();
//獲取Query對象
Query query =session.createQuery("from Book order by id desc");
//調用Query對象的方法,獲取結果集
List list = query.list();
//遍歷結果集
for(Object obj : list){
System.out.println(obj);
}
tx.commit();//提交事務
}
}
在執行的過程中出現了兩個異常
- Caused by: java.lang.ClassNotFoundException: org.hibernate.engine.FilterDefinition
改爲org.springframework.orm.hibernate5.LocalSessionFactoryBean
即可 - 如果使用sessionFactory.getCurrentSession()獲取session會報
Could not obtain transaction-synchronized Session for current thread
可參考原因及解決方案
Spring 整合 Hibernate,提供 HibernateTemplate 簡化原始Hibernate代碼 (不用關心session,你只需要用就行了)
HibernateDaoSupport 是spring-orm-4.2.4.RELEASE.jar
下的包
public class BookDaoImpl extends HibernateDaoSupport {
//保存圖書
public void save(Book book){
//注入sesson工廠,獲取session--不會寫了
//因爲:spring提供了模版類,來整合Hibernate
super.getHibernateTemplate().save(book);
}
//更新圖書(根據id)
public void update(Book book){
super.getHibernateTemplate().update(book);
}
//刪除圖書(根據id)
public void delete(Book book){
super.getHibernateTemplate().delete(book);
}
//根據id查詢
public Book findById(Integer id){
return super.getHibernateTemplate().get(Book.class, id);
}
//查詢所有
public List<Book> findAll(){
return super.getHibernateTemplate().loadAll(Book.class);
// return super.getHibernateTemplate().find("from Book");//hql方式
}
//複雜條件條件
//1.命名查詢:QBN
public List<Book> findByNamedQuery(String queryName, Object... values){
return super.getHibernateTemplate().findByNamedQuery(queryName, values);
}
//2.離線條件查詢:QBC
public List<Book> findByCriteria(DetachedCriteria criteria){
return super.getHibernateTemplate().findByCriteria(criteria);
}
}
applicationContext.xml
<!-- dao -->
<bean id="bookDao" class="cn.itcast.dao.BookDao">
<!-- 注入sessionFactory,這是在Dao層使用hibernateTemplate的條件,用來操作數據庫的crud -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
測試:
@RunWith(SpringJUnit4ClassRunner.class) //junit整合spring的測試//立馬開啓了spring的註解
@ContextConfiguration(locations="classpath:applicationContext.xml") //加載核心配置文件,自動構建spring容器
public class SpringTest {
@Autowired
private SessionFactory sessionFactory;
@Autowired
private BookDao bookDao;
@Test
public void testHibernate(){
bookDao.save(new Book(null, "金瓶梅", 16d)); //報異常
// Book book = bookDao.findById(1); //可執行
}
}
我們會發現還是會報錯 org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.
這是因爲如果我們不對這個sessionFactory
配置事務,默認只能執行查詢的操作(所以在執行增傷改時報異常)
擴展3:配置事務
可參考(spring第四天)
- 配置事務管理器平臺
- 事務管理通知
- 事務的切入點和切面配置
(1)配置事務管理器平臺 HibernateTransactionManager
因爲用了hibernate5.LocalSessionFactoryBean
,所以這裏的事務管理器也要改成hibernate5下的事務管理器
org.springframework.orm.hibernate5.HibernateTransactionManager
Spring爲不同的持久化框架提供了不同PlatformTransactionManager接口實現:
事務 | 說明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用Spring JDBC或iBatis 進行持久化數據時使用 |
org.springframework.orm.hibernate5.HibernateTransactionManager | 使用Hibernate5.0版本進行持久化數據時使用 |
org.springframework.orm.jpa.JpaTransactionManager | 使用JPA進行持久化時使用 |
org.springframework.jdo.JdoTransactionManager | 當持久化機制是Jdo時使用 |
org.springframework.transaction.jta.JtaTransactionManager | 使用一個JTA實現來管理事務,在一個事務跨越多個資源時必須使用 |
- DataSourceTransactionManager針對JdbcTemplate、MyBatis 事務控制 ,使用Connection(連接)進行事務控制 :
開啓事務 connection.setAutoCommit(false);
提交事務 connection.commit();
回滾事務 connection.rollback(); - HibernateTransactionManager針對Hibernate框架進行事務管理,使用Session的Transaction相關操作進行事務控制 :
開啓事務 session.beginTransaction();
提交事務 session.getTransaction().commit();
回滾事務 session.getTransaction().rollback()
事務管理器的選擇?用戶根據選擇和使用的持久層技術,來選擇對應的事務管理器。
此處我們使用了Hibernate作爲持久化技術
(2)事務管理通知
spring支持兩種事務管理方式
編程式事務管理
要修改代碼,所以不常用聲明式事務管理
使用XML或註解配置聲明式事務
Spring的聲明式事務是通過AOP實現的(環繞通知)
聲明式事務也有兩種實現方式
-
XML配置方式添加事務管理
(tx、aop元素)
-
註解配置方式添加事務管理
@Transactional
瞭解(spring第四天)
實現方式一:XML配置方式添加事務管理`(tx、aop元素)
<!-- 第一步:定義具體的平臺事務管理器(DataSource事務管理器) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入數據源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 第二步:定義通知,通知中要處理的就是事務 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事務的屬性定義 -->
<tx:attributes>
<!-- 配置具體的方法的事務屬性
isolation//事務的隔離級別,默認是按數據庫的隔離級別來
propagation//事務的傳播行爲,默認是同一個事務
timeout="-1":事務的超時時間,默認值使用數據庫的超時時間。
read-only="false":事務是否只讀,默認可讀寫。
rollback-for:遇到哪些異常就回滾,其他的都不回滾
no-rollback-for:遇到哪些異常不回滾,其他的都回滾。和上面互斥的
-->
<tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
<!-- 支持通配符
要求service中 方法名字必須符合下面的規則
-->
<tx:method name="save*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 第三步:配置切入點,讓通知關聯切入點,即事務控制業務層的方法 -->
<aop:config>
<!-- 切入點 -->
<aop:pointcut expression="bean(*Service)" id="txPointcut"/>
<!-- 切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<!-- dao -->
<bean id="accountDao" class="cn.itcast.spring.dao.AccountDaoImpl">
<!-- 注入數據源,才擁有jdbctemplate -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 業務層 -->
<bean id="accountService" class="cn.itcast.spring.service.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"/>
</bean>
實現方式二:註解配置方式添加事務管理 @Transactional
步驟:
1.在需要管理事務的方法或者類上面 添加@Transactional 註解
2.配置註解驅動事務管理
(事務管理註解生效的作用)(需要配置對特定持久層框架使用的事務管理器)
在applicationContext.xml中開啓事務註解驅動
<!-- 第一步:定義具體的平臺事務管理器(DataSource事務管理器) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入數據源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事務註解驅動 :識別事務的註解@tr。。。
transaction-manager:具體的平臺事務管理器
-->
<!-- <tx:annotation-driven transaction-manager="transactionManager"/> -->
<!-- 默認的平臺事務管理器的名字叫:transactionManager,此時transaction-manager="transactionManager"可以不寫 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在需要添加事務管理的方法或類上添加@Transactional
public class AccountServiceImpl implements IAccountService {
//注入dao
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Transactional(readOnly=false)
@Override
public void transfer(String outName, String inName, Double money) {
//調用dao層
//先取出
accountDao.out(outName, money);
//再轉入
int i = 1/0;
accountDao.in(inName, money);
}
}
測試
@Autowired
private IAccountService accountService;
@Test
public void test1(){
accountService.transfer("Tom", "Jack", 1000d);
System.out.println("轉賬成功!");
}
配置事務的定義屬性信息,在註解中直接配置:
事務的傳播行爲:
測試:
public class UserDao extends JdbcDaoSupport {
@Autowired
private RoleDao roleDao;
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = Exception.class)
public void testTranstation() throws Exception {
String sql = "insert into user values(?,?)";
super.getJdbcTemplate().update(sql, "wxf", "wxf");
// roleDao對象調用方法
roleDao.testTranstationREQUIRES_NEW();// user回滾 role不回滾
// 異常
int a = 1 / 0;
}
}
public class RoleDao extends JdbcDaoSupport {
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT, rollbackFor = RuntimeException.class)
public void testTranstationREQUIRES_NEW() throws RuntimeException, SQLException {
String sql = "insert into role values(?,?)";
super.getJdbcTemplate().update(sql, "test", "test");
}
}
@Autowired
private UserDao userDao;
@Test
public void test() throws Exception {
userDao.testTranstation();
}
執行結果如下:user回滾 role不回滾
原理:
- 當UserDao中有
@Transactional註解
,spring容器在創建對象時,掃描到此註解,就會通過動態代理創建代理對象
此處的userDao是一個代理對象,它的類型是cn.itcast.dao.UserDao$$EnhancerBySpringCGLIB$$aaf591c2
,是一個通過cglib代理對象
當我們刪除UserDao中的@Transactional註解
,創建的就是一個class cn.itcast.dao.UserDao
- 所以我們在執行
testTranstation()
實際上執行的是代理對象的方法(通過AOP進行增強,此處的增強是被事務管理) - 在SpringUnit中,我們注入的userDao是代理對象,同時userDao中的roleDao也是代理對象,(兩個對象都被spring管理,spring掃描到有
@Transactional註解
,就創建了代理對象),所以userDao.testTranstation()
執行的是代理對象的方法(AOP增強,此處的增強就是被事務管理),roleDao.testTranstationREQUIRES_NEW()
同理 REQUIRED
(支持當前事務,如果不存在 就新建一個),REQUIRES_NEW
(如果有事務存在,掛起當前事務,創建一個新的事務),所以testTranstationREQUIRES_NEW()
方法有自己的事務,當 testTranstation()
方法遇到異常,只回滾自己事務中對數據庫的操作
此時我們再做一個實驗,把testTranstationREQUIRES_NEW()
方法放到UserDao類中(同一個類中事務方法調用事務方法
)
public class UserDao extends JdbcDaoSupport {
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = Exception.class)
public void testTranstation() throws Exception {
String sql = "insert into user values(?,?)";
int update = super.getJdbcTemplate().update(sql, "wxf", "wxf");
// 調用同類的方法
testTranstationREQUIRES_NEW();
// 異常
int a = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT, rollbackFor = RuntimeException.class)
public void testTranstationREQUIRES_NEW() throws RuntimeException {
String sql = "insert into role values(?,?)";
int update = super.getJdbcTemplate().update(sql, "test", "test");
}
}
@Autowired
private UserDao userDao;
@Test
public void test() throws Exception {
userDao.testTranstation();
}
執行結果如下:user回滾 role回滾
原理:
- 此處的userDao依舊是代理對象,在執行
userDao.testTranstation()
,我們看一下其中的this
的類型,我們發現this
是class cn.itcast.dao.UserDao
類型,this就是被代理的對象
,我們可以回過頭看下AOP的動態代理 - 既然this不是代理對象,所以執行
this.testTranstationREQUIRES_NEW()
也就不會被aop增強
簡而言之:
- 在同一個類中,一個方法調用另外一個有註解(比如@Async,@Transational)的方法,註解是不會生效的。
問:JDBCTemple底層是使用了connection,那麼每一個dao中的JDBCTemple使用的connection是否是同一個,如果不是同一個,如何做到事務管理的
(5) 導入實體類和映射文件
修改applicationContext.xml 引入hbm映射
<!-- 第三部分: 引入hbm -->
<property name="mappingResources">
<list>
<value>cn/itcast/storemanager/domain/Goods.hbm.xml</value>
<value>cn/itcast/storemanager/domain/History.hbm.xml</value>
<value>cn/itcast/storemanager/domain/Store.hbm.xml</value>
<value>cn/itcast/storemanager/domain/Userinfo.hbm.xml</value>
</list>
</property>
【擴展知識】批量映射配置:
<!-- 可以批量引入hbm
支持通配符
-->
<property name="mappingLocations">
<list>
<value>classpath:cn/itcast/storemanager/domain/*.hbm.xml</value>
</list>
</property>
3.登錄功能的實現(三層架構搭建、通用代碼抽取、基於泛型的通用Dao)
【業務邏輯梳理】
用戶在頁面輸入用戶和密碼 -----> 數據封裝model —>- 通過Service傳遞到Dao 查詢數據庫 —> 返回Userinfo對象 ---->
如果正確,將登錄信息保存到session,
如果不正確,封裝ActionError 返回登錄頁面。
3.1 修改登錄頁面表單
使用struts2 標籤,因爲需要回顯功能,新增struts標籤的taglib:
<%@ taglib uri="/struts-tags" prefix="s" %>
修改原來的form:
<s:form name="loginForm" action="user_login" namespace="/" method="post" cssStyle="margin-top:250px;">
......
</s:form>
注意:struts2標籤使用的標準的html標籤,因此,屬性必須加雙引號。
修改其他標籤:
用戶名:<s:textfield name="name" cssClass="tx" maxLength="15" size="15" />
密碼:<s:password name="password" cssClass="tx" maxLength="15" size="15"/>
增加驗證信息顯示標籤:
<TR>
<TD align="center" colSpan=3 width="623" height="260"
background="<c:url value='/picture/welcome_01.gif'/>">
<!-- 驗證碼返回提示 --> <br> <br> <br> <br> <br>
<font color="#ff60a0" size="5">
<!-- 回顯錯誤表單信息:表單校驗錯誤,針對具體字段 -->
<s:fielderror></s:fielderror>
<!-- 回顯一般錯誤信息 -->
<s:actionerror/>
</font>
</TD>
</TR>
3.2 編寫UserAction (表現層 )
簡化抽取Action類,讓Action類繼承BaseAction:
優勢:
- 使得所有Action都繼承ActionSupport。
- 實現模型驅動的接口全部放置到BaseAction類中完成。
- 將Action傳遞值的操作封裝到BaseAction中完成
第一步:在cn.itcast.storemanager.web.action中創建BaseAction的類:代碼:
//action的父類:用來存放action的重複代碼的
//通用:泛型
public abstract class BaseAction<T> extends ActionSupport implements ModelDriven<T>{
//實例化數據模型T,模型驅動必須實例化
// private T t = new T();
//子類可見
protected T model;//沒有初始化
public T getModel() {
return model;
}
//在默認的構造器初始化數據模型
public BaseAction() {
//在子類初始化的時候,默認會調用父類的構造器
//反射機制:獲取具體的類型
//得到帶有泛型的類型,如BaseAction<Userinfo>
Type superclass = this.getClass().getGenericSuperclass();
//轉換爲參數化類型
ParameterizedType parameterizedType = (ParameterizedType) superclass;
//獲取泛型的第一個參數的類型類,如Userinfo
Class<T> modelClass = (Class<T>) parameterizedType.getActualTypeArguments()[0];
//實例化數據模型類型
try {
model = modelClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//封裝值棧的操作的方法
//root棧:棧頂map,可以通過key獲取value
protected void setToValueStackRoot(String key,Object value){
ActionContext.getContext().getValueStack().set(key, value);
}
//root棧:棧頂對象(匿名)
protected void pushToValueStackRoot(Object value){
ActionContext.getContext().getValueStack().push(value);
}
//map棧:--推薦,可以通過key獲取value
protected void putToValueStackMap(String key,Object value){
ActionContext.getContext().put(key, value);
}
//root棧:action的屬性
protected Object result;
public Object getResult() {//action在root棧,因此,result也在root棧
return result;
}
}
繼承ActionSupport
繼承ActionSupport是創建action的三種方法之一
實現ModelDriven<T>
實現ModelDriven<T>請求參數的接收機制中的模型驅動
第二步:創建UserAction子類代碼:用來接收頁面傳遞的user_login。
//用戶操作的action
//public class UserAction extends ActionSupport implements ModelDriven<Userinfo>{
public class UserAction extends BaseAction<Userinfo>{
// //數據模型對象,在BaseAction中體現
// private Userinfo userinfo = new Userinfo();
// @Override
// public Userinfo getModel() {
// return userinfo;
// }
//注入service
private IUserService userService;
public void setUserService(IUserService userService) {
this.userService = userService;
}
//業務方法:登錄
public String login(){
System.out.println("用戶開始登錄了。。。。"+model);
//調用業務層,查詢
Userinfo loginUser= userService.login(model);
if(loginUser == null){
//登錄失敗
//將失敗信息打印頁面
addActionError("您的用戶名或密碼錯誤請重試");
//跳轉到登錄
return "loginjsp";
}else{
//登錄成功
//將登錄用戶放入session
// ServletActionContext.getRequest().getSession().setAttribute("loginUser", loginUser);
ServletUtils.setLoginUserToSession(loginUser);
//代碼:可以抽取到baseAction,也可以抽取到工具類中
//跳轉到主頁
return SUCCESS;
}
}
}
簡單分析:
當子類實例化的時候,會將具體的類型來代替參數化父類的T,並自動執行父類的構造方法,通過反射機制,得到具體的模型的實例。struts通過getModel得到模型對象。
開發小技巧:
1.可以先將代碼寫完再統一配置(因爲配置的時候需要代碼)
2.代碼可以從前往後寫(從頁面–>Action–>Service–>Dao),也可以從後往前寫(先寫Dao,再寫Action和Service)。
第三步:在cn.itcast.storemanager.utils包中抽取工具類ServletUtils.java:
//操作servlet相關
public class ServletUtils {
//登錄用戶的key
private static final String LOGIN_USER="loginUser";//常量
//將登陸了用戶放入session
//滿足登錄和退出
public static void setLoginUserToSession(Userinfo loginUser){
// ServletActionContext.getRequest().getSession().setAttribute("loginUser", loginUser);
if(null==loginUser){
//清除登錄用戶
ServletActionContext.getRequest().getSession().removeAttribute(LOGIN_USER);
}else{
//放登錄用戶
ServletActionContext.getRequest().getSession().setAttribute(LOGIN_USER, loginUser);
}
}
//從session獲取登錄用戶信息
public static Userinfo getLoginUserFromSession(){
// return (Userinfo)ServletActionContext.getRequest().getSession().getAttribute("loginUser");
Object o = ServletActionContext.getRequest().getSession().getAttribute(LOGIN_USER);
return o == null?null:(Userinfo)o;
}
}