依然是思路最重要,这次是修改一个对象,很显然,必须要知道需要修改哪个对象,然后将对象展示在一个表单里供修改,修改完成后更新数据库,返回列表页。
经过思考可以发现,将数据保存入数据库,和我们编写的新增代码本质上一样,只是保存的内容不是新的东西,而是从数据库中取出的对象。所以首先要解决的问题就是如果知道修改哪一个。
通过项目一开始的界面介绍可以知道,为每个数据添加一个链接,在生成列表的时候确定好每个链接附带的id,通过id就可以知道要修改哪个对象了。
修改列表页添加操作列
为每一行数据添加一个操作链接,在其中附带上这行对应的数据库中的id就可以了,修改list-customers.jsp:
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Action</th>
</tr>
<c:forEach var="tempCustomer" items="${customers}">
<!--创建一个updateLink变量,使用GET参数customerId=id-->
<c:url var="updateLink" value="/customer/showFormForUpdate">
<c:param name="customerId" value="${tempCustomer.id}"/>
</c:url>
<tr>
<td> ${tempCustomer.firstName} </td>
<td> ${tempCustomer.lastName} </td>
<td> ${tempCustomer.email} </td>
<td><a href="${updateLink}">Update</a> </td>
</tr>
</c:forEach>
</table>
在table标签内加了Action列,然后我们通过JSTL标签库做了一个GET请求的链接地址,然后将这个链接放到每一行的Action列中。
在项目运行后,页面中的实际链接是这样:
http://localhost:8080/customer/showFormForUpdate?customerId=1
可以看一下这条链接与JSTL标签里属性的对应关系。这样我们编写对应地址的控制器,就可以拿到id数据,从而展示和更新数据。
编写控制器和视图
很显然,要编写一个和GET请求地址对应的控制器,从请求中取得id值,还记得直接绑定request中的参数吗:
@GetMapping(value = "/customer/showFormForUpdate")
public String updateCustomer(@RequestParam("customerId") int customerId, Model model) {
//获取customerId对应的customer对象
Customer customer = customerService.getCustomer(customerId);
//将customer对象设置到model上去
model.addAttribute("customer", customer);
return "customer-form";
}
这里直接将请求中的customerId对应的值绑定给控制器方法的参数,然后先取出对象,再将对象设置到model上,很显然,这里要为Service和DAO层再编写一系列抽象方法及实现了。
这里省略掉接口抽象方法,DAO和Service层如下:
//DAO
@Override
public Customer getCustomer(int customerId) {
Session session = sessionFactory.getCurrentSession();
return session.get(Customer.class, customerId);
}
//Service
@Override
@Transactional
public Customer getCustomer(int customerId) {
return customerDAO.getCustomer(customerId);
}
更新customer方法和新增customer方法都使用了customer-form.jsp,不同之处是新增customer的时候传递的是一个新的对象,而更新的时候传递的是一个已知的对象。由于在之前我们知道表单标签在生成页面的时候调用getter方法,提交的时候调用setter方法,那么表单应该会显示出要修改的内容,提交后就修改成功。
启动项目后测试成功,发现可以修改。但是这里还有一点要注意,如果表单是直接通过链接生成的,或者服务中断,很有可能不知道到底是新的数据还是老的数据,所以一般的做法是在页面中放一个隐藏的input标签,用于记录id,如果有id就说明当前页面内的数据是一个老数据,如果没有id,就是一个新数据。
修改customer-form.jsp,在form:form
标签内添加一行:
<form:hidden path="id"/>
之后还需要一处至关重要的地方,就是修改DAO层的saveCustomer方法:
@Override
public void saveCustomer(Customer customer) {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(customer);
}
.saveOrUpdate(customer)
方法会自动判断这个数据对象的主键,如果存在就更新,不存在就新建。
这样我们通过一个隐藏字段,就彻底杜绝了可能出现的数据错误。在日常的Web开发中,这种做法是非常普遍的,可以避免页面加载不完全,或者反复提交等问题。
删除Customer
这个项目写到这份上也基本上知道删除该怎么做了,次序分别是在列表页添加删除按钮--编写控制器处理删除请求--到数据库中删除--返回列表页。
当然,一点就删除这个还是危险了点,有很多库可以实现一个对话框来判断,这里我们简单一些,就用一小段JS代码来确认。
创建删除链接
删除链接依然使用JSTL来生成:
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Action</th>
</tr>
<c:forEach var="tempCustomer" items="${customers}">
<c:url var="updateLink" value="/customer/showFormForUpdate">
<c:param name="customerId" value="${tempCustomer.id}"/>
</c:url>
<c:url var="deleteLink" value="/customer/delete">
<c:param name="customerId" value="${tempCustomer.id}"/>
</c:url>
<tr>
<td> ${tempCustomer.firstName} </td>
<td> ${tempCustomer.lastName} </td>
<td> ${tempCustomer.email} </td>
<td><a href="${updateLink}">Update</a>|<a href="${deleteLink}" onclick="if(!(confirm('确定删除吗?' ))) return false">Delete</a></td>
</tr>
</c:forEach>
</table>
使用id生成了一个删除链接,传递给delete视图控制器。
编写控制器,Service和DAO层方法
先是控制器,套路已经很熟悉了
@GetMapping(value = "/delete")
public String deleteCustomer(@RequestParam("customerId") int customerId) {
customerService.deleteCustomer(customerId);
return "redirect:/customer/list";
}
之后是Service和DAO层,接口的代码就省略了。
//Service
@Override
@Transactional
public void deleteCustomer(int customerId) {
customerDAO.deleteCustomer(customerId);
}
//DAO
@Override
public void deleteCustomer(int customerId) {
Session session = sessionFactory.getCurrentSession();
Customer customer = session.get(Customer.class, customerId);
session.delete(customer);
}
实验一下,搞定啦。第一个增删改查项目顺利完工。
这个项目还有几个可以改进的地方:
- 为表单和Entity Class添加验证功能,在控制器类里加上预处理器
- 将域注入都改成依赖注入
- 按照Customer的名字进行模糊查询
这些改进的功能如何实现,其实都已经有了解决方法。现在继续学习Spring的另外一个重要的内容:AOC。