💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
和上节的思想一样。我们如下开发: 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)