和上節的思想一樣。我們如下開發:
1. 定制路由
2. 開發C
3. 開發V
4. 測試C與V是否成功對接
5. 開發M
6. 測試M
7. 對接C與M
8. 對接V與C
9. 集成測試
在現階段,可以說所有的開發步驟都離不開上述的9步。
# 定制路由
```
<!-- 更新數據 -->
<action name="update" class="teacher.Update">
<result name="success">/jsp/success.jsp</result>
<result name="error">/jsp/error.jsp</result>
</action>
```
# C
```
package teacher;
import entity.Teacher;
public class Update {
private int id;
private String name;
private String username;
private Boolean sex;
private String password;
private String email;
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 getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Boolean getSex() {
return sex;
}
public void setSex(String sex) {
if (sex.equals("0")) {
this.sex = false;
} else {
this.sex = true;
}
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
// 該execute方法將被自動調用, 方法的返回類型必須為String
public String execute() {
// 獲取要編輯的教師,并實例化
Teacher teacher = new Teacher(id, name, username, email, sex, password);
// 打印查看獲取到的信息
System.out.println(teacher.toString());
return "success";
}
}
```
# V
V層文件,我們仍然使用統一的success.jsp. 已存在。無需更改.
# 測試C與V是否成功對接

# M
```
public Boolean update() {
// 創建會話(這里的session也是會話的意思,我們以前接觸的http中的session,處理的是用戶與服務器的對話)
Session session = MysqlJavaee.getCurrentSession();
// 開啟事務(使用緩沖池進行數據庫的連接)
Transaction transaction = session.beginTransaction();
// 在這里,必須使用try catch finally語句。來確定會話正常關閉.
// 否則,當操作數據庫產生錯誤時,你可能需要重啟mysql服務
try {
// 獲取對象
// 使用Teacher.class來獲取到Teacher的類名(包括包名)
Teacher teacher = (Teacher) session.get(Teacher.class, id);
// 賦值
teacher.setName(name);
teacher.setEmail(email);
teacher.setPassword(password);
teacher.setSex(sex);
// 更新
session.update(teacher);
// 提交事務
transaction.commit();
// 捕獲異常
} catch (HibernateException e) {
// 如果事務執行異常,則回滾事務
if (null != transaction) {
transaction.rollback();
}
// 打印異常
e.printStackTrace();
} finally {
// 如果session處于開啟狀態,則關閉session
if (session.isOpen()) {
// 關閉會話
session.close();
}
}
return true;
}
```
## 測試
```
@Test
public void update() {
Teacher teacher = Teacher.getTeacherById(1);
System.out.println("更新操作前");
System.out.println(teacher.toString());
// 更新
teacher = new Teacher(1, "this is lisi", "zhangsan", "zhangsan@yunzhiclub.com", false, "123");
teacher.update();
// 查看結果
teacher = Teacher.getTeacherById(1);
System.out.println("更新操作后");
System.out.println(teacher.toString());
}
```
我們可以對`(1, "this is zhangsan", "zhangsan", "zhangsan@yunzhiclub.com", false, "123")`的值進行更改,然后執行測試并查看控制臺,同時查看數據表中的數據是否做了更新(初期的時候很重要).
# C對接M
```
// 該execute方法將被自動調用, 方法的返回類型必須為String
public String execute() {
// 獲取要編輯的教師,并實例化
Teacher teacher = new Teacher(id, name, username, email, sex, password);
// 更新
teacher.update();
return "success";
}
```
# V對接C
```
// 該execute方法將被自動調用, 方法的返回類型必須為String
public String execute() {
// 獲取要編輯的教師,并實例化
Teacher teacher = new Teacher(id, name, username, email, sex, password);
// 更新
teacher.update();
return "success";
}
```
# 集成測試
[http://localhost:8080/javaee/teacher/edit?id=1](http://localhost:8080/javaee/teacher/edit?id=1), 添加數據后點擊submit,查看數據表是否進行更新操作.
# 重構代碼
如果你前面學習夠扎實的話,相信肯定能夠方法,我們在update()方法中獲取要更新的對象時,是完全可以使用`getTeacherById`來替換`Teacher teacher = (Teacher) session.get(Teacher.class, id);`的。從軟件工程的角度上來講,我們也應該這樣做。原因就不用說了吧:不造相同的輪子.
現在,讓我們修改并測試一下。
## 代碼修改
```
// 獲取對象
// 使用Teacher.class來獲取到Teacher的類名(包括包名)
Teacher teacher = this.getTeacherById(id);
// 賦值
teacher.setName(name);
```
## 單元測試
進行單元測試,我們得到了一個異常:
```
更新操作前
Teacher [id=1, name=hello, username=zhangsan, email=zhangsan@yunzhiclub.com, sex=false, password=]
org.hibernate.TransactionException: nested transactions not supported
Hibernate: select teacher0_.id as id1_0_0_, teacher0_.email as email2_0_0_, teacher0_.name as name3_0_0_, teacher0_.password as password4_0_0_, teacher0_.sex as sex5_0_0_, teacher0_.username as username6_0_0_ from Teacher teacher0_ where teacher0_.id=?
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.begin(AbstractTransactionImpl.java:152)
at org.hibernate.internal.SessionImpl.beginTransaction(SessionImpl.java:1426)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.hibernate.context.internal.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:352)
at com.sun.proxy.$Proxy9.beginTransaction(Unknown Source)
at entity.Teacher.getTeacherById(Teacher.java:276)
at entity.Teacher.update(Teacher.java:318)
at entity.TestTeacher.update(TestTeacher.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
更新操作后
```
有說人,老師這怎么看呀,根本抓不住重點。是的,如果把它當成作文來看,當然抓不住重點了。如果抓重點呢?
> 只看自己負責的代碼部分的異常。
本著這個思想,我們刪除沒有用的信息后:
```
更新操作前
Teacher [id=1, name=hello, username=zhangsan, email=zhangsan@yunzhiclub.com, sex=false, password=]
org.hibernate.TransactionException: nested transactions not supported
at entity.Teacher.getTeacherById(Teacher.java:276)
at entity.Teacher.update(Teacher.java:318)
at entity.TestTeacher.update(TestTeacher.java:35)
```
現在異常明朗了。
我們知道,自己先執行了TestTeacher.update, 然后執行了Teacher.update,再然后執行了:eacher.getTeacherById。看來:異常的輸出順序與執行順序正好相反。
把自己的代碼單獨的列出來,最終我們發現,問題出在了Teacher.getTeacherById(Teacher.java:276),我們在控制臺中,單擊跳轉到該行,發現原來是這句:`Transaction transaction = session.beginTransaction();`
# BUG產生的原因
報的問題是:nested transactions not supported
好的,報錯行找到了,提示找到了,下面我們開始GOOGLE:nested transactions not supported
如果這4個單詞你有不認識的,那么你在GOOGLE前,首先要做的是,去翻譯這行英文為:不支持事務嵌套使用
能過查詢我們明白了。他的意思好像是這樣。
以下使用正確:
```
事務開始
事務結束
事務再開始
事務再結束
事務再再再開始
事務再再再結束
```
以下使用錯誤:
```
事務開始
事務開始
事務結束
事務結束
```
找到了錯誤,我們再分析代碼。
由于我們在進行session處理里,采用的單例模式。即:張三找我要輔導員,我把王五指定給了他;李四找我要輔導員,我還是給他的王五。
所以,在update中,我們已經開啟了事務,而此時,我們又調用getTeacherById方法,當這個方法再開始事務的時候。就出現事務嵌套,最終引發了上述BUG。
# 引用server層徹底解決bug
知道了上述的原因,就不能解決了。由于在數據實體類的方法中,需要使用事務來保證數據操作的安全性。而每個事務都應該是獨立存在,所以在實體中,不能出現互相調用的情況。為了避免這種情況,同時,又不會造相同的輪子。我們增加一個server來解決這個問題。
## 重構實體
```
package entity;
...
// 聲明主體
@Entity
public class Teacher {
public Boolean update() {
// 創建會話(這里的session也是會話的意思,我們以前接觸的http中的session,處理的是用戶與服務器的對話)
Session session = MysqlJavaee.getCurrentSession();
// 開啟事務(使用緩沖池進行數據庫的連接)
Transaction transaction = session.beginTransaction();
// 在這里,必須使用try catch finally語句。來確定會話正常關閉.
// 否則,當操作數據庫產生錯誤時,你可能需要重啟mysql服務
try {
// 更新
session.update(this);
// 提交事務
transaction.commit();
// 捕獲異常
} catch (HibernateException e) {
// 如果事務執行異常,則回滾事務
if (null != transaction) {
transaction.rollback();
}
// 打印異常
e.printStackTrace();
} finally {
// 如果session處于開啟狀態,則關閉session
if (session.isOpen()) {
// 關閉會話
session.close();
}
}
return true;
}
...
```
### 測試
```
@Test
public void update() {
Teacher teacher = Teacher.getTeacherById(1);
System.out.println("更新操作前");
System.out.println(teacher.toString());
// 更新
teacher.setName("hello1");
teacher.update();
// 查看結果
teacher = Teacher.getTeacherById(1);
System.out.println("更新操作后");
System.out.println(teacher.toString());
}
```
控制臺:
```
更新操作前
Teacher [id=1, name=hello, username=zhangsan, email=zhangsan@yunzhiclub.com, sex=false, password=]
Hibernate: update Teacher set email=?, name=?, password=?, sex=?, username=? where id=?
Hibernate: select teacher0_.id as id1_0_0_, teacher0_.email as email2_0_0_, teacher0_.name as name3_0_0_, teacher0_.password as password4_0_0_, teacher0_.sex as sex5_0_0_, teacher0_.username as username6_0_0_ from Teacher teacher0_ where teacher0_.id=?
更新操作后
Teacher [id=1, name=hello1, username=zhangsan, email=zhangsan@yunzhiclub.com, sex=false, password=]
Hibernate: select teacher0_.id as id1_0_0_, teacher0_.email as email2_0_0_, teacher0_.name as name3_0_0_, teacher0_.password as password4_0_0_, teacher0_.sex as sex5_0_0_, teacher0_.username as username6_0_0_ from Teacher teacher0_ where teacher0_.id=?
Teacher [id=1, name=hello1, username=zhangsan, email=zhangsan@yunzhiclub.com, sex=false, password=]
class entity.Teacher
```
## 建立server包
我們在src右建,新建一個package,并起名為server
## 建立TeacherServer文件
在package上右建,新建一個class,并起名為TeacherServer,代碼如下:
```
package server;
import entity.Teacher;
public class TeacherServer {
public Boolean update(Teacher newTeacher) {
// 獲取數據表數據
Teacher teacher = Teacher.getTeacherById(newTeacher.getId());
// 賦值
teacher.setEmail(newTeacher.getEmail());
teacher.setName(newTeacher.getName());
teacher.setUsername(newTeacher.getUsername());
teacher.setPassword(newTeacher.getPassword());
teacher.setSex(newTeacher.getSex());
// 更新并返回
teacher.update();
return true;
}
}
```
### 單元測試
同樣,我們在test目錄中,新建一個package, 命名為server,并新建一個TeacherServerTest測試類.
代碼如下:
```
package server;
import org.junit.Test;
import entity.Teacher;
public class TeacherServerTest {
@Test
public void update() {
Teacher teacher = Teacher.getTeacherById(1);
System.out.println("更新操作前");
System.out.println(teacher.toString());
// 更新
teacher = new Teacher(1, "this is zhangsan", "zhangsan", "zhangsan@yunzhiclub.com", false, "123");
TeacherServer.update(teacher);
// 查看結果
teacher = Teacher.getTeacherById(1);
System.out.println("更新操作后");
System.out.println(teacher.toString());
}
}
```
控制臺:
```
更新操作前
Teacher [id=1, name=hello1, username=zhangsan, email=zhangsan@yunzhiclub.com, sex=false, password=]
Hibernate: select teacher0_.id as id1_0_0_, teacher0_.email as email2_0_0_, teacher0_.name as name3_0_0_, teacher0_.password as password4_0_0_, teacher0_.sex as sex5_0_0_, teacher0_.username as username6_0_0_ from Teacher teacher0_ where teacher0_.id=?
Hibernate: update Teacher set email=?, name=?, password=?, sex=?, username=? where id=?
Hibernate: select teacher0_.id as id1_0_0_, teacher0_.email as email2_0_0_, teacher0_.name as name3_0_0_, teacher0_.password as password4_0_0_, teacher0_.sex as sex5_0_0_, teacher0_.username as username6_0_0_ from Teacher teacher0_ where teacher0_.id=?
更新操作后
Teacher [id=1, name=this is zhangsan, username=zhangsan, email=zhangsan@yunzhiclub.com, sex=false, password=123]
```
## 對接C層
Update.java
```
// 該execute方法將被自動調用, 方法的返回類型必須為String
public String execute() {
// 獲取要編輯的教師,并實例化
Teacher teacher = new Teacher(id, name, username, email, sex, password);
// 更新
TeacherServer.update(teacher);
return "success";
}
```
## 集成測試
[http://localhost:8080/javaee/teacher/edit?id=1](http://localhost:8080/javaee/teacher/edit?id=1)輸入信息后提交,并查看控制臺與數據庫.
** 自下節開始,我們開始拋棄在C層中,直接調用entity的方法。改為C調用server,server再調用entity。**
> 本節參考官方文檔:[#objectstate-modifying](https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/objectstate.html#objectstate-modifying)
- README
- 第一章:準備
- 第二章:Hello World!
- 第一節:查看工程文件
- 第二節:JDK、JRE與環境變量
- 第三節:index.jsp
- 第三章:Hello Struts
- 第一節:Web.xml
- 第二節:單入口
- 第三節:Hello Struts
- 第四節:觸發C層
- 第四章:建立數據表
- 第一節:建立實體類
- 第二節:測試一
- 第三節:測試二
- 第四節:引入Hibernate
- 第五節:配置Hibernate
- 第六節:建立連接
- 第七節:實體類映射數據表
- 第八節:完善數據表
- 第五章:教師管理
- 第一節:增加數據--add
- 第二節:增加數據--save
- 1 獲取傳入數據數據
- 2 數據寫入測試
- 3 對接C層
- 第三節:數據列表
- 1 獲取數據
- 2 重構代碼
- 3 C層對接--初始化
- 4 C層添加數據
- 5 V層顯示數據
- 6 獲取數據庫中數據
- 7 顯示性別
- 8 分頁
- 9 條件查詢
- 第四節:修改數據
- 1 edit
- 2 update
- 第五節:刪除數據
- 第六節:總結
- 第六章:重構C層
- 第一節:繼承ActionSupport類
- 第二節:數據驗證
- 第七章:前臺分離(前臺)
- 第一節:環境搭建
- 第二節:運行環境
- 第三節:共享開發環境
- 第四節:生產環境
- 第八章:前臺開發(前臺)
- 第一節:本地化
- 第二節:教師列表
- 1 引入M層
- 2 模擬后臺返回數據
- 3 C與M對接
- 4 C與V對接
- 第九章:前后臺對接(前后臺)
- 第一節:后臺輸出json(后臺)
- 第二節:對接前臺(全棧)
- 第二節:對接API(前臺)
- 第二節:跨域請求(后臺)
- 第三節:重構代碼(前臺)
- 第十章:重構后臺M層
- 第一節:數據訪問DAO層
- 第二節:項目整體重構
- 第十一章:用戶登陸(前后臺)
- 第一節:制定規范
- 第二節:定制測試用例
- 第三節:后臺輸入測試代碼(后臺)
- 第四節:postman(后臺)
- 第五節:新建用戶登陸模塊(前臺)
- 第六節:代碼重構(前臺)
- 第十二章:班級管理(前后臺)
- 第一節:班級列表
- 1 原型開發
- 2 制定規范
- 3 后臺對接開發
- 4 前臺對接開發
- 第二節:Add
- 1 原型開發
- 2 制定規范
- 3 后臺對接開發
- 4 前臺對接開發
- 第三節:Save
- 1 制定規范
- 2 后臺對接開發
- 3 前臺對接開發
- 第四節:Edit
- 1 原型開發
- 2 制定規范
- 3 后臺對接開發
- 4 前臺對接開發
- 第五節:Update
- 1 制定規范
- 2 后臺對接開發
- 3 前臺對接開發
- 第六節:Delete
- 1 制定規范
- 2 后臺對接開發
- 3 前臺對接開發
- 第七節:小結
- 第十三章:班級管理(API)
- 第一節:ER圖
- 第二節:create
- 1 實體層
- 2 dao層
- 3 service(server)層
- 4 action層
- 第三節:ManyToOne
- 第四節:Read
- 1 service(server)層
- 2 action層
- 第五節:update
- 1 service(server)層
- 2 action層
- 第六節:update
- 第十四章:重構服務層