下午好。在这篇文章中,我想分享我在创建一个简单的 CRUD 应用程序的过程中第一次接触到 Maven、Spring、Hibernate、MySQL 和 Tomcat 等东西。这是 4 的第二部分。本文主要面向那些已经完成 30-40 级,但尚未超越纯 Java 并刚刚开始(或即将开始)进入开放世界的人所有这些技术、框架和其他陌生的词汇。 这是文章《Maven、Spring、MySQL、Hibernate 简介和第一个 CRUD 应用程序》的第二部分。第一部分可以通过以下链接查看: Maven、Spring、MySQL、Hibernate 和第一个 CRUD 应用程序简介(第 1 部分)
内容:
好吧,让我们继续,现在让我们尝试构建一个完整的电影存储库。当然,在我们的小而简单的应用程序中,您可以简单地愚蠢地将所有逻辑放在控制器中,但是,正如已经指出的,最好立即学习如何正确地完成所有操作。因此,让我们创建几个层。我们将有一个负责处理数据的DAO ,一个 Service,其中会有各种其他逻辑,而Controller将仅处理请求并调用必要的服务方法。数据访问对象
数据访问对象(DAO)就是这样的一种设计模式。重点是创建一个特殊层,专门负责访问数据(使用数据库或其他存储机制)。在包中dao
我们将创建一个接口FilmDAO
,其中会有添加、删除等方法。我对它们的称呼略有不同,但它们对应于基本的CRUD操作(C reate、Read、U pdate、D elete)。
这里值得注意的是,除了DAO之外,还有Repository这样的方法,它们看起来非常相似,都是用于处理数据。我还没有弄清楚这些方法有什么特点以及它们之间有什么区别。因此,我在这里可能会弄错,这应该称为存储库,而不是道,或者可能是介于两者之间的东西。但在我见过和研究过的大多数例子中,这被称为 DAO,所以我可能会这样称呼它。同时,也许在文本的更远的地方我会使用“存储库”这个词。无论如何,如果我有什么地方说错了,请原谅我。 |
package testgroup.filmography.dao;
import testgroup.filmography.model.Film;
import java.util.List;
public interface FilmDAO {
List<Film> allFilms();
void add(Film film);
void delete(Film film);
void edit(Film film);
Film getById(int id);
}
现在我们需要它的实现。我们还不会连接数据库,这还是有点可怕。为了练习和习惯它,我们首先模拟内存中的存储并创建一个包含几部电影的列表。为了存储列表,我们将使用 not List
, but Map
,以便方便地通过其检索特定电影id
,而无需遍历整个列表。对于生成,id
我们使用AtomicInteger。让我们创建一个类FilmDAOImpl
,实现所有方法并填写地图。类似的事情。
package testgroup.filmography.dao;
import testgroup.filmography.model.Film;
import java.util.*;
public class FilmDAOImpl implements FilmDAO {
private static final AtomicInteger AUTO_ID = new AtomicInteger(0);
private static Map<Integer, Film> films = new HashMap<>();
static {
Film film1 = new Film();
film1.setId(AUTO_ID.getAndIncrement());
film1.setTitle("Inception");
film1.setYear(2010);
film1.setGenre("sci-fi");
film1.setWatched(true);
films.put(film1.getId(), film1);
// + film2, film3, film4, ...
}
@Override
public List<Film> allFilms() {
return new ArrayList<>(films.values());
}
@Override
public void add(Film film) {
film.setId(AUTO_ID.getAndIncrement());
films.put(film.getId(), film);
}
@Override
public void delete(Film film) {
films.remove(film.getId());
}
@Override
public void edit(Film film) {
films.put(film.getId(), film);
}
@Override
public Film getById(int id) {
return films.get(id);
}
}
服务
现在让我们添加一个服务层。原则上,在这个例子中,很可能没有它,将我们限制在 DAO 上;应用程序将非常简单,并且没有计划在服务中使用任何复杂的逻辑。但突然之间,你会想在项目中添加各种复杂的东西和有趣的东西,所以为了完整起见,就这样吧。目前,它将简单地调用 DAO 中的方法。service
让我们在包中创建一个接口FilmService
。
package testgroup.filmography.service;
import testgroup.filmography.model.Film;
import java.util.List;
public interface FilmService {
List<Film> allFilms();
void add(Film film);
void delete(Film film);
void edit(Film film);
Film getById(int id);
}
及其实现:
package testgroup.filmography.service;
import testgroup.filmography.dao.FilmDAO;
import testgroup.filmography.dao.FilmDAOImpl;
import testgroup.filmography.model.Film;
import java.util.List;
public class FilmServiceImpl implements FilmService {
private FilmDAO filmDAO = new FilmDAOImpl();
@Override
public List<Film> allFilms() {
return filmDAO.allFilms();
}
@Override
public void add(Film film) {
filmDAO.add(film);
}
@Override
public void delete(Film film) {
filmDAO.delete(film);
}
@Override
public void edit(Film film) {
filmDAO.edit(film);
}
@Override
public Film getById(int id) {
return filmDAO.getById(id);
}
}
项目结构现在如下所示:
控制器和视图
现在让我们研究控制器方法并填充页面。在填写页面时,我们需要一些技巧。例如,要显示电影列表,我们需要一个循环,如果我们想要更改一些铭文,根据参数,我们需要条件等。JSP(JavaServer Pages)格式允许您使用 Java 代码插入来实现这一切。但我不想在页面上使用java代码和HTML代码混合。至少,这将是非常丑陋的。幸运的是,为了解决这个问题,有一个很棒的东西,比如JSTL(JavaServer Pages 标准标记库)或 JSP 标准标记库。它允许我们在 JSP 页面中使用大量附加标签来满足各种需求。让我们将其连接到pom.xml
:
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
现在让我们看一下控制器。首先,让我们从那里删除对象的创建Film
,这是为了测试而完成的,我们不需要任何其他东西。让我们在那里添加一个服务并调用它的方法。
public class FilmController {
private FilmService filmService = new FilmServiceImpl();
好吧,相应地我们将为每种情况创建方法,添加、删除等。首先是显示带有电影列表的主页的方法:
@RequestMapping(method = RequestMethod.GET)
public ModelAndView allFilms() {
List<Film> films = filmService.allFilms();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("films");
modelAndView.addObject("filmsList", films);
return modelAndView;
}
这里没有什么新鲜事。我们从服务中获取电影列表并将其添加到模型中。films.jsp
现在让我们制作此方法返回的 主页:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>FILMS</title>
</head>
<body>
<h2>Films</h2>
<table>
<tr>
<th>id</th>
<th>title</th>
<th>year</th>
<th>genre</th>
<th>watched</th>
<th>action</th>
</tr>
<c:forEach var="film" items="${filmsList}">
<tr>
<td>${film.id}</td>
<td>${film.title}</td>
<td>${film.year}</td>
<td>${film.genre}</td>
<td>${film.watched}</td>
<td>
<a href="/edit/${film.id}">edit</a>
<a href="/delete/${film.id}">delete</a>
</td>
</tr>
</c:forEach>
</table>
<h2>Add</h2>
<c:url value="/add" var="add"/>
<a href="${add}">Add new film</a>
</body>
</html>
让我们仔细看看这个页面,看看有什么。 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - 这里连接的是JSTL核心,其中包括创建循环、条件等主要标签。
<table>
— 用于创建表的标签。<tr>
— 表格行<th>
- 列标题<td>
— 表格单元格
<c:forEach var="film" items="${filmsList}">
,在一个循环(我们从 JSTL 核心获取)中,我们遍历传递列表 ( filmsList
) 的所有元素,对于每个元素 ( film
),我们创建一个新行并将相应的值写入每个单元格。这里有一点,录音似乎film.id
需要理解为film.getId()
,即 不直接访问该字段,而是调用 getter。在最后一列(action
)中,我们创建用于删除和编辑的链接(我们现在将创建相应的方法)。那么,下面是添加新电影的方法的链接。它是这样的: 接下来,让我们看一下返回特定电影的编辑页面的方法:
@RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
public ModelAndView editPage(@PathVariable("id") int id) {
Film film = filmService.getById(id);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("editPage");
modelAndView.addObject("film", film);
return modelAndView;
}
这里出现了一些新的东西——这是一个注释@PathVariable
。表示该参数( int id
)是从地址栏获取的。为了指示这个参数在地址栏中的位置,使用了构造{id}
(顺便说一句,如果变量名相同,就像本例一样,那么就不必在括号中指示,只需写@PathVariable int id
)。因此,在主页上我们为每部电影制作了链接,表明id
:
<a href="/edit/${film.id}">edit</a>
然后将该值分配给方法参数,然后我们使用它通过服务从存储库获取特定的电影并将其添加到模型中。这是获取编辑页面的方法,现在我们需要一个编辑本身的方法:
@RequestMapping(value = "/edit", method = RequestMethod.POST)
public ModelAndView editFilm(@ModelAttribute("film") Film film) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/");
filmService.edit(film);
return modelAndView;
}
在该方法中,editPage
我们向模型添加了一个属性:
modelAndView.addObject("film", filmService.getById(id));
现在,在注释的帮助下,@ModelAttribute
我们获得了这个属性,并且可以更改它。请求方法,POST
因为这里我们将传递数据。“ redirect:/
”表示执行该方法后我们将被重定向到地址“ /
”,即 该方法将运行allFilms
,我们将返回主页。现在让我们制作页面本身editPage.jsp
:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Edit</title>
</head>
<body>
<c:url value="/edit" var="var"/>
<form action="${var}" method="POST">
<input type="hidden" name="id" value="${film.id}">
<label for="title">Title</label>
<input type="text" name="title" id="title">
<label for="year">Year</label>
<input type="text" name="year" id="year">
<label for="genre">Genre</label>
<input type="text" name="genre" id="genre">
<label for="watched">Watched</label>
<input type="text" name="watched" id="watched">
<input type="submit" value="Edit film">
</form>
</body>
</html>
<form>
— 用于收集和发送数据的表格,表明谁将处理该数据 (/edit
)<input>
— 用户交互的界面元素(按钮、输入字段等)<label>
- 文字标签
<input type="submit" value="Edit film">
表单中的数据将被发送到服务器(专门添加了一个带有值的不可见字段,id
以便服务器知道数据库中的哪条记录需要更新)。在方法中editFilm
它们将被分配到相应的属性字段film
。然后我们将返回主页并提供更新的列表。编辑页面如下所示: 现在让我们开始将新电影添加到列表中。为此,您还需要一个用于输入和提交数据的表单。您可以在主页上制作一个表单,也可以制作一个单独的页面,例如editPage.jsp
. 但是,另一方面,添加的形式与编辑的形式完全相同,即 4 个输入字段和一个提交按钮。那么为什么要创建一个新页面呢,让我们使用editPage.jsp
. 获取页面的方法:
@RequestMapping(value = "/add", method = RequestMethod.GET)
public ModelAndView addPage() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("editPage");
return modelAndView;
}
在该方法中,editPage
我们额外传递了该属性以便稍后更改它,但这里我们只是接收页面。以及添加方法:
@RequestMapping(value = "/add", method = RequestMethod.POST)
public ModelAndView addFilm(@ModelAttribute("film") Film film) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/");
filmService.add(film);
return modelAndView;
}
由于我们这里没有传递属性,所以这里会创建一个新的对象Film
。嗯,这里基本上没有什么新东西。还值得注意的是,我们在“ /add
”中提供了这两种方法。这是可能的,因为它们响应不同类型的请求。通过点击主页上的链接,我们发出 GET 请求,这会将我们引导至addPage
. 当在添加页面上单击按钮发送数据时,会发出 POST 请求,并且addFilm
. 为了添加新电影,我们决定使用与编辑相同的页面。但数据被发送到地址“ /edit
”:
<c:url value="/edit" var="var"/>
<form action="${var}" method="POST">
<input type="submit" value="Edit film">
</form>
我们需要稍微调整页面,使其在添加和编辑时表现不同。为了解决这个问题,我们将使用同一 JSTL 核心库中的条件:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<c:if test="${empty film.title}">
<title>Add</title>
</c:if>
<c:if test="${!empty film.title}">
<title>Edit</title>
</c:if>
</head>
<body>
<c:if test="${empty film.title}">
<c:url value="/add" var="var"/>
</c:if>
<c:if test="${!empty film.title}">
<c:url value="/edit" var="var"/>
</c:if>
<form action="${var}" method="POST">
<c:if test="${!empty film.title}">
<input type="hidden" name="id" value="${film.id}">
</c:if>
<label for="title">Title</label>
<input type="text" name="title" id="title">
<label for="year">Year</label>
<input type="text" name="year" id="year">
<label for="genre">Genre</label>
<input type="text" name="genre" id="genre">
<label for="watched">Watched</label>
<input type="text" name="watched" id="watched">
<c:if test="${empty film.title}">
<input type="submit" value="Add new film">
</c:if>
<c:if test="${!empty film.title}">
<input type="submit" value="Edit film">
</c:if>
</form>
</body>
</html>
那些。我们只是检查一下现场film.title
。如果它是空的,那么它是一部新电影,我们必须填写它的所有数据并将其添加到列表中。如果此字段不为空,则它是列表中的电影,您只需更改它即可。那。我们得到了页面的两个版本: 好吧,最后一个用于从列表中删除电影的控制器方法:
@RequestMapping(value="/delete/{id}", method = RequestMethod.GET)
public ModelAndView deleteFilm(@PathVariable("id") int id) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/");
Film film = filmService.getById(id);
filmService.delete(film);
return modelAndView;
}
我想这里没有必要评论什么,这一切都已经考虑过了。我们已经在主页上建立了指向该地址的链接。好了,这里一切似乎都已准备就绪,您可以再次运行它,看看一切如何运作。
作为 Spring 组件的存储库和服务
让我们再做一个小修正。事实上,现在我们的存储和服务只是类,为了使用它们,我们必须自己创建一个类对象(new FilmServiceImpl()
)。但我们连接Spring是有原因的,所以让他自己控制这件事。为了将我们的类置于 Spring 的控制之下,我们需要表明它们是组件。为此,我们用特殊注释来标记它们:
@Repository
public class FilmDAOImpl implements FilmDAO {
@Service
public class FilmServiceImpl implements FilmService {
注释@Repository
和@Service
以及 均@Controller
源自@Component
。这三个注释的具体功能和差异是什么以及它们与简单组件的区别应在文档或指南中单独阅读。现在,知道这些注释告诉 Spring 这些类分别是存储库和服务就足够了。现在我们不再需要自己创建这些类的具体对象:
private FilmService filmService = new FilmServiceImpl();
相反,您可以使用特殊注释来标记该字段,Spring 将选择适当的实现:
@Autowired
private FilmService filmService;
注释@Autowired
(自动绑定)告诉 Spring 它应该深入了解其上下文并在此处替换合适的 bean。非常舒服。如果说以前我们使用接口是为了不用担心方法的具体实现的话,那么现在我们甚至不需要担心接口本身的实现,甚至不需要知道它的名字。这个想法是不建议在字段上使用自动绑定;最好使用构造函数或设置器。在文档中阅读有关此内容的更多信息。对于我们来说,原则上,这并不重要,我们可以放心地就这样离开。但是,既然这个想法是要求的,我们就会尊重一切都是美丽的,没有任何黄色警告。在控制器类中,我们创建一个 setter 并对其进行注释:
@Controller
public class FilmController {
private FilmService filmService;
@Autowired
public void setFilmService(FilmService filmService) {
this.filmService = filmService;
}
同样,我们FilmDAO
在类中创建了一个 setter FilmServiceImpl
。 待续... Maven、Spring、MySQL、Hibernate 简介和第一个 CRUD 应用程序(第 1 部分) Maven、Spring、MySQL、Hibernate 简介和第一个 CRUD 应用程序(第 2 部分) Maven、Spring、MySQL、Hibernate 简介和第一个 CRUD 应用程序(第 3 部分) Maven、Spring、MySQL、Hibernate 简介和第一个 CRUD 应用程序(第 4 部分)
GO TO FULL VERSION