和上节的思想一样。我们如下开发:
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是否成功对接
![测试](https://box.kancloud.cn/1ad2f21377bd630b321d4145d9029318_549x222.gif)
# 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
- 第十四章:重构服务层