版权提醒
本系列文章为原创内容。转载请注明文章来源。
前言
本文为搭建 SSSP 框架极简书店教程系列的其中一篇。本文主要内容:配置 SpringDataJpa 相关部分并实现网站后台书籍管理功能。
至此,所有 SSSP(Spring+Struts2+SpringDataJpa)框架的整合,以及其与 JavaWeb 项目的配合等重要知识点已经全部讲完。如果需要其他部分的代码,请参考在 Github 中的项目源码。
配置 SpringDataJpa
建立数据库信息统一管理文件
在 resource 目录中建立 db.properties 文件。
db.datebase=h2
db.driverClassName=org.h2.Driver
db.userName=sa
db.password=123456
db.url=jdbc:h2:~/Bookstore
配置 Spring 配置文件
向 applicationContext.xml 中添加以下内容:
<!-- 引入数据库配置文件 db.properties -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 数据库连接池连接设置 -->
<bean id="dataSource" class="org.h2.jdbcx.JdbcConnectionPool" factory-method="create" destroy-method="dispose">
<constructor-arg index="0" value="${db.url}"/>
<constructor-arg index="1" value="${db.userName}"/>
<constructor-arg index="2" value="${db.password}"/>
</bean>
<!-- hibernate 的 Jpa 实现 -->
<bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
<jpa:repositories base-package="com.chiyu.dao"
transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory"/>
<!-- 配置针对 JPA 的局部事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
<!-- SpringDataJpa设置 -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
</property>
<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/>
<property name="packagesToScan" value="com.chiyu.entity"/>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
<!-- 显示SQL -->
<prop key="hibernate.show_sql">ture</prop>
<!-- 格式化SQL -->
<prop key="hibernate.format_sql">ture</prop>
<!-- 生成SQL注释 -->
<prop key="hibernate.use_sql_comments">ture</prop>
<!-- 配置如何根据java模型生成数据库表结构 -->
<prop key="hibernate.hbm2ddl.auto">validate</prop>
<!-- 配置表名字段的习惯,采用下划线,例如My_Name -> MyName -->
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
</props>
</property>
</bean>
完成 DAO 层
可以说,使用 SpringDataJpa 的优势在此处表现地淋漓尽致了。下面是 BookDao.java 的全部代码。
欸嘿,就是这么简单!因为具体操作 SpringDataJpa 已经帮我们完成了!
package com.chiyu.dao;
import com.chiyu.entity.BookEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface BookDao extends JpaRepository<BookEntity, String> {
}
完成 Service 层
首先写接口:BookService。
有没有奇怪为什么 insert 操作与 update 操作使用同样的方法?
package com.chiyu.service;
import com.chiyu.entity.BookEntity;
import java.util.List;
public interface BookService {
//插入或更新一条图书信息
BookEntity insertOrUpdateBook(BookEntity book);
//删除一条图书信息
void deleteBook(String id);
//获取所有图书信息
List<BookEntity> findAllBooks();
//寻找指定书号的图书信息
BookEntity findBookById(String id);
}
接下来我们在 service 包下建立 Impl 包,存放 service 接口的实现类。在 Impl 包中写一个类实现这个接口:BookServiceImpl 。
package com.chiyu.service.Impl;
import com.chiyu.dao.BookDao;
import com.chiyu.entity.BookEntity;
import com.chiyu.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public BookDao getBookDao() {
return bookDao;
}
//使用Spring Bean自动赋值
@Autowired
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
@Override
public BookEntity insertOrUpdateBook(BookEntity book) {
return bookDao.save(book);
}
@Override
public void deleteBook(String id) {
bookDao.deleteById(id);
}
@Override
public List<BookEntity> findAllBooks() {
return bookDao.findAll();
}
@Override
public BookEntity findBookById(String id) {
return bookDao.findById(id).orElse(null);
}
}
是不是很神奇?对数据库书籍表的增删改查工作,我们从未写过,但我们就是可以直接用!感谢 SpringDataJpa 。
同时,回答上面的问题,插入与更新使用同一个方法,是因为 SpringDataJpa 中,这两个操作都使用save()
方法,区别两种操作的方法,是要保存的项的主键在数据库中是否已经存在。
SpringDataJpa 的使用并不需要写 SQL 语句,而是依靠方法名,例如:
findBookById(String id)
,如果把 find 改为 select ,是不是看到了 SQL 的影子?如果需要进行其他对数据库的操作,需要使用对应的方法名。方法名命名规则可以参考SpringDataJpa 官方文档。
.orElse(null)
方法并不是 SpringDataJpa 中定义的,因为在执行数据库中查找操作时,可能找不到匹配的项,所以 SpringDataJpa 中查找操作返回的数据类型为 java.util.Optional ,即可以为空的类型。使用.orElse(null)
方法,可以让 Optional 类型直接返回原始数据类型,如果为空,则返回 null 。
完成 Action 层
建立 后台书籍管理 Action :BookManager 。
@Autowired
使用 Spring Bean 为此 set 方法对应的变量赋值。
其他变量的 get 和 set 方法用于实现 Struts 的 Action 与 jsp 页面的数据传递。
InputStream
用于完成 AJAX 操作后向请求方返回数据。
package com.chiyu.action;
import com.chiyu.entity.BookEntity;
import com.chiyu.service.BookService;
import com.opensymphony.xwork2.ActionSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
@Controller
public class BookManager extends ActionSupport {
//用于获取从页面返回的书籍信息。
private BookEntity book;
private List<BookEntity> bookList;
//用于对书籍进行各种操作的服务层,由Spring Bean赋值
private BookService bookService;
//用于使用AJAX技术执行书籍添加删除更新时向网页传递信息
private InputStream inputStream;
public BookEntity getBook() {
return book;
}
public void setBook(BookEntity book) {
this.book = book;
}
public List<BookEntity> getBookList() {
return bookList;
}
public void setBookList(List<BookEntity> bookList) {
this.bookList = bookList;
}
public BookService getBookService() {
return bookService;
}
@Autowired
public void setBookService(BookService bookService) {
this.bookService = bookService;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
//访问书籍管理页面,并返回所有书籍信息。
@Override
public String execute() throws Exception {
//返回所有书籍信息
bookList = bookService.findAllBooks();
return SUCCESS;
}
//添加或更新图书方法,使用AJAX
public String addOrUpdateBook(){
bookService.insertOrUpdateBook(book);
String result = "操作成功";
inputStream=new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8));
return "addOrUpdateBookResult";
}
//删除图书方法,使用AJAX
public String deleteBook(){
bookService.deleteBook(book.getBid());
String result = "删除成功";
inputStream=new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8));
return "deleteBookResult";
}
}
配置 Struts 配置文件
在 background.xml 文件中添加以下内容:
<action name="bookManager" class="bookManager">
<result name="success">/admin/admin_book.jsp</result>
<result name="addOrUpdateBookResult" type="stream">
<!-- 设置返回给浏览器的数据类型 -->
<param name="contentType">text/html</param>
<!--指定获取InputStream的方法,getInputStream(),约定:去掉get,后面部分使用camel写法 -->
<param name="inputName">inputStream</param>
</result>
<result name="deleteBookResult" type="stream">
<!-- 设置返回给浏览器的数据类型 -->
<param name="contentType">text/html</param>
<!--指定获取InputStream的方法,getInputStream(),约定:去掉get,后面部分使用camel写法 -->
<param name="inputName">inputStream</param>
</result>
</action>
下面两个 result 用于指定向 AJAX 请求发起方返回的数据类型和数据名。
完成书籍管理页面
admin_book.jsp 全部代码如下:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!doctype html>
<html lang="zh-cmn-Hans">
<head>
<title>后台-书籍管理</title>
<!-- 引入CSS样式和JS脚本文件等-->
<%@include file="common/admin_headContext.jsp"%>
</head>
<body>
<!-- 引入顶部导航条-->
<%@include file="common/admin_common_top.jsp"%>
<div class="container-fluid">
<div class="row">
<!-- 引入左侧导航栏-->
<%@include file="common/admin_common_left.jsp"%>
<!-- 页面主体 -->
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">书籍总览</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="mr-2">
<button type="button" class="btn btn-sm btn-outline-secondary" id="addBookButton">新增书籍</button>
</div>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">书号</th>
<th scope="col">书名</th>
<th scope="col">单价</th>
<th scope="col">作者</th>
<th scope="col">出版社</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody id="bookList">
<s:iterator value="bookList">
<tr>
<th scope="row"><s:property value="bid"/></th>
<td><s:property value="bname"/></td>
<td><s:property value="price"/></td>
<td><s:property value="author"/></td>
<td><s:property value="press"/></td>
<td>
<div class="btn-group mr-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="updateBookFunc(this)">修改</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="deleteBookFunc(this)">删除</button>
</div>
</td>
</tr>
</s:iterator>
</tbody>
</table>
</main>
</div>
</div>
<script type="text/javascript">
<!-- 设置左侧导航栏为正确选中项-->
$(document).ready(function(){
$("#book").addClass("active");
});
<!--对应添加按钮-->
$("#addBookButton").click(function() {
$("#bookList").append("<tr id='newLine'><th scope=\"row\"><input id='input_bid' type=\"text\" name=\"book.bid\" placeholder=\"书号\" required autofocus></th> <td><input id='input_bname' type=\"text\" name=\"book.bname\" placeholder=\"书名\" required></td> <td><input id='input_price' type=\"text\" name=\"book.price\" placeholder=\"单价\" required></td> <td><input id='input_author' type=\"text\" name=\"book.author\" placeholder=\"作者\" required></td> <td><input id='input_press' type=\"text\" name=\"book.press\" placeholder=\"出版社\" required></td> <td> <button onclick='submitBookFunc(this)' type=\"button\" class=\"btn btn-sm btn-outline-secondary deleteBookButton\">提交</button> </td></tr>");
//只有在一条新记录提交完成后,才能创建下一条新的记录。
$(this).attr("disabled",true);
});
<!--对应添加提交按钮-->
function submitBookFunc(btn){
//防止多次提交
$(btn).attr("disabled",true);
let bid = $("#input_bid").val();
let bname = $("#input_bname").val();
let price = $("#input_price").val();
let author = $("#input_author").val();
let press = $("#input_press").val();
$.ajax({
url:"bookManager!addOrUpdateBook.action",
type:"post",
data:{"book.bid":bid,"book.bname":bname,"book.price":price,"book.author":author,"book.press":press},
dataType:"text",
error:function (){
alert("操作失败!");
},
success:function (data){
alert(data);
if (data === "操作成功"){
//只有在一条新记录提交完成后,才能创建下一条新的记录。
$("#addBookButton").attr("disabled",false);
$("#newLine").remove();
$("#bookList").append("<tr><th scope='row'>" + bid+ "</th> <td>" + bname + "</td> <td>" + price + "</td> <td>" + author + "</td> <td>" + press + "</td> <td> <div class='btn-group mr-2'> <button type='button' class='btn btn-sm btn-outline-secondary' onclick='updateBookFunc(this)'>修改</button><button type='button' class='btn btn-sm btn-outline-secondary' onclick='deleteBookFunc(this)'>删除</button></div></td></tr>'");
}
}
})
}
<!--对应修改按钮-->
function updateBookFunc(btn){
let item = $(btn).parents("div").parent("td").siblings("th");
//防止多次提交
$(btn).attr("disabled",true);
let bid = item.text();
let bname = item.next().text();
let price = item.next().next().text();
let author = item.next().next().next().text();
let press = item.next().next().next().next().text();
item.parents("tr").remove();
$("#bookList").append("<tr id='newLine'><th scope=\"row\"><input id='input_bid' type=\"text\" name=\"book.bid\" value=" + bid + " required disabled></th> <td><input id='input_bname' type=\"text\" name=\"book.bname\" value=" + bname + " required></td> <td><input id='input_price' type=\"text\" name=\"book.price\" value=" + price + " required></td> <td><input id='input_author' type=\"text\" name=\"book.author\" value=" + author + " required></td> <td><input id='input_press' type=\"text\" name=\"book.press\" value=" + press + " required></td> <td> <button onclick='submitBookFunc(this)' type=\"button\" class=\"btn btn-sm btn-outline-secondary deleteBookButton\">提交</button> </td></tr>");
}
<!--对应删除按钮-->
function deleteBookFunc(btn){
if(confirm('确定要删除该行信息?')){
let item = $(btn).parents("div").parent("td").siblings("th");
//防止多次提交
$(btn).attr("disabled",true);
let bid = item.text();
$.ajax({
url:"bookManager!deleteBook.action",
type:"post",
data:{"book.bid":bid},
dataType:"text",
error:function (){
alert("删除失败!");
},
success:function (data){
alert(data);
if (data === "删除成功"){
$(btn).parents("tr").remove();
}else {
$(btn).attr("disabled",false);
}
}
})
}
}
</script>
</body>
</html>
在 JSP 中接收 struts 中的 List 类型数据并遍历
这部分是解释上面 jsp 文件的部分内容。
"bookList" 是 struts 中 List 变量的变量名,"bid"是 List 中存储的项的数据类型中的变量名。
<s:iterator value="bookList">
<tr>
<th scope="row"><s:property value="bid"/></th>
<td><s:property value="bname"/></td>
<td><s:property value="price"/></td>
<td><s:property value="author"/></td>
<td><s:property value="press"/></td>
<td>
<div class="btn-group mr-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="updateBookFunc(this)">修改</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="deleteBookFunc(this)">删除</button>
</div>
</td>
</tr>
</s:iterator>
使用 AJAX 技术
这部分是解释上面 jsp 文件的部分内容。
function submitBookFunc(btn){
//防止多次提交
$(btn).attr("disabled",true);
let bid = $("#input_bid").val();
let bname = $("#input_bname").val();
let price = $("#input_price").val();
let author = $("#input_author").val();
let press = $("#input_press").val();
$.ajax({
url:"bookManager!addOrUpdateBook.action",
type:"post",
data:{"book.bid":bid,"book.bname":bname,"book.price":price,"book.author":author,"book.press":press},
dataType:"text",
error:function (){
alert("操作失败!");
},
success:function (data){
alert(data);
if (data === "操作成功"){
//只有在一条新记录提交完成后,才能创建下一条新的记录。
$("#addBookButton").attr("disabled",false);
$("#newLine").remove();
$("#bookList").append("<tr><th scope='row'>" + bid+ "</th> <td>" + bname + "</td> <td>" + price + "</td> <td>" + author + "</td> <td>" + press + "</td> <td> <div class='btn-group mr-2'> <button type='button' class='btn btn-sm btn-outline-secondary' onclick='updateBookFunc(this)'>修改</button><button type='button' class='btn btn-sm btn-outline-secondary' onclick='deleteBookFunc(this)'>删除</button></div></td></tr>'");
}
}
})
}
将 JSON 作为 Ajax 的返回值类型
这部分是解释上面 jsp 文件的部分内容。
见本博客文章如何在 Struts2 中将 JSON 作为 Ajax 的返回值类型 。
配置相关文件使管理员点击“书籍管理”后跳转到书籍管理
在 BackgroundDispatcher 类中添加以下方法:
//到达后台书籍管理。
public String toBookManage() {
return "toBookManage";
}
修改 background.xml
<package name="background" namespace="/admin" extends="struts-default" strict-method-invocation="false">
<!--后台总调度-->
<action name="index" class="backgroundDispatcher">
<result name="input">/admin/admin_login.jsp</result>
<result name="success">/admin/admin_welcome.jsp</result>
<result name="login">/admin/admin_login.jsp</result>
<result name="toBookManage" type="redirectAction">bookManager.action</result>
</action>
<!--管理员登录相关-->
<action name="toLogin" class="adminLogin">
<result name="success" type="redirectAction">index.action</result>
<result name="input">/admin/admin_login.jsp</result>
</action>
<!--书籍管理-->
<action name="bookManager" class="bookManager">
<result name="success">/admin/admin_book.jsp</result>
<result name="addOrUpdateBookResult" type="stream">
<!-- 设置返回给浏览器的数据类型 -->
<param name="contentType">text/html</param>
<!--指定获取InputStream的方法,getInputStream(),约定:去掉get,后面部分使用camel写法 -->
<param name="inputName">inputStream</param>
</result>
<result name="deleteBookResult" type="stream">
<!-- 设置返回给浏览器的数据类型 -->
<param name="contentType">text/html</param>
<!--指定获取InputStream的方法,getInputStream(),约定:去掉get,后面部分使用camel写法 -->
<param name="inputName">inputStream</param>
</result>
</action>
</package>
运行项目
现在,在登录后台,点击“书籍管理”,你应该可以看到从数据库获取的书籍信息。
现在项目所需的主要知识以及介绍完毕,开始你的创作吧!