下午好。在这篇文章中,我想分享我在创建一个简单的 CRUD 应用程序的过程中第一次接触到 Maven、Spring、Hibernate、MySQL 和 Tomcat 等东西。这是 4 的第三部分。本文主要面向那些已经完成 30-40 级,但尚未超越纯 Java 并刚刚开始(或即将开始)进入开放世界的人所有这些技术、框架和其他陌生的词语。 这是文章《Maven、Spring、MySQL、Hibernate 简介和第一个 CRUD 应用程序》的第三部分。前面的部分可以通过以下链接查看:
- Maven、Spring、MySQL、Hibernate 和第一个 CRUD 应用程序简介(第 1 部分)
- Maven、Spring、MySQL、Hibernate 和第一个 CRUD 应用程序简介(第 2 部分)
内容:
创建并连接数据库
好吧,是时候开始处理数据库了。在连接Hibernate并思考它应该如何在那里工作之前,首先让我们看看数据库本身,即 让我们创建它、连接它、制作它并填写标志。我们将使用DBMS(数据库管理系统)MySQL(当然,您必须首先下载并安装)。 SQL(结构化查询语言)是一种声明性编程语言,用于创建、修改和操作关系数据库中的数据。在此类数据库中,数据以表的形式存储。应用程序如何与数据库通信(将SQL查询传输到数据库并返回结果)。为此,Java 提供了JDBC(Java 数据库连接)这样的东西,简单地说,它是一组用于处理数据库的接口和类。要与数据库交互,您需要创建一个连接;为此,包java.sql
有一个类Connection
。建立连接的方法有多种,例如可以使用getConnection
类方法DriverManager
。但是与数据库的交互并不是直接进行的,因为数据库有很多,而且各不相同。因此,它们每个都有自己的 JDBC 驱动程序。使用该驱动程序,可以建立与数据库的连接。所以,首先,为了以后不被这个分心,我们先安装MySQL驱动。让我们添加pom.xml
以下依赖项:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
现在让我们创建数据库。 查看 -> 工具窗口 -> 数据库- 将打开数据库面板。 新建(绿色+)->数据源->MySQL - 将打开一个窗口,您需要在其中指定用户名和密码,我们在安装MySQL时设置它们(例如,我使用root和root)。端口(MySQL 默认为 3306)、名称等。保持原样。您可以使用“测试连接”按钮测试连接。 单击“确定”,现在我们已连接到 MySQL 服务器。接下来,让我们创建一个数据库。为此,您可以在打开的控制台中编写一个脚本:
CREATE DATABASE test
单击“执行”,数据库已准备就绪,现在您可以连接它。为此,请返回“数据源属性”并在“数据库”字段中输入数据库名称(test),然后再次输入用户名和密码,然后单击“确定”。现在我们需要制作一张桌子。您可以使用图形工具,但第一次,可能值得手动编写脚本来看看它是什么样子:
USE test;
CREATE TABLE films
(
id int(10) PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(100) NOT NULL,
year int(4),
genre VARCHAR(20),
watched BIT DEFAULT false NOT NULL
)
COLLATE='utf8_general_ci';
CREATE UNIQUE INDEX films_title_uindex ON films (title);
INSERT INTO `films` (`title`,`year`,`genre`, watched)
VALUES
("Inception", 2010, "sci-fi", 1),
("The Lord of the Rings: The Fellowship of the Ring", 2001, "fantasy", 1),
("Tag", 2018, "comedy", 0),
("Gunfight at the O.K. Corral", 1957, "western", 0),
("Die Hard", 1988, "action", 1);
创建一个表,其名称films
为 columns等id
。title
对于每一列,都会指示类型(括号中是最大输出大小)。
PRIMARY KEY
- 这是主键,用于唯一标识表中的记录(意味着唯一性)AUTO_INCREMENT
— 该值将自动生成(当然它将是非零的,因此您不必指定它)NOT NULL
- 这里一切也很明显,它不能为空DEFAULT
— 设置默认值COLLATE
- 编码CREATE UNIQUE INDEX
— 使该领域独一无二INSERT INTO
— 添加一条记录到表中
main
暂时创建一个方法。原则上,你可以把它放在任何地方,甚至在控制器类中,甚至在模型或配置中,都没关系,你只需要使用它来确保连接一切正常,然后就可以删除它。但为了更加小心,让我们为它创建一个单独的类Main
:
package testgroup.filmography;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Main {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/test";
String username = "root";
String password = "root";
System.out.println("Connecting...");
try (Connection connection = DriverManager.getConnection(url, username, password)) {
System.out.println("Connection successful!");
} catch (SQLException e) {
System.out.println("Connection failed!");
e.printStackTrace();
}
}
}
这里一切都很简单,我们设置数据库的连接参数并尝试创建连接。让我们启动这个main
并看一下。因此,我遇到了异常、一些时区问题以及一些有关 SSL 的其他警告。浏览互联网后,您可以发现这是一个相当普遍的问题,并且当使用不同版本的驱动程序(mysql-connector-java)时,它可以发誓不同。例如,我通过实验发现,使用5.1.47版本时,没有因为时区的原因出现异常,连接正常创建,但仍然弹出SSL警告。对于其他一些版本,似乎存在有关 SSL 的例外,而不仅仅是警告。好吧,这不是重点。你可以尝试单独处理这个问题,但我们现在不讨论它。解决这个问题的方法非常简单,您需要在url中指定其他参数,即serverTimezone
问题是否与时区有关,以及useSSL
是否与 SSL 有关:
String url = "jdbc:mysql://localhost:3306/test?serverTimezone=Europe/Minsk&useSSL=false";
现在我们已经设置了时区并禁用了 SSL。我们再次启动它main
,瞧 - 连接成功!好吧,太好了,我们找到了如何创建连接。该类Main
已基本完成其任务,可以将其删除。
ORM 和 JPA
好的方面,为了更好的理解,最好是按顺序开始熟悉数据库,从一开始,没有任何hibernate之类的东西。因此,最好找到一些指南并首先尝试使用 JDBC 类、手动编写 SQL 查询等。好吧,让我们立即转向ORM模型。这是什么意思?当然,再次建议单独阅读这一点,但我将尝试简要描述它。ORM(Object-Relational Mapping 或对象关系映射)是一种将对象映射到关系数据库结构的技术,即 将我们的 Java 对象表示为表行。感谢 ORM,您不必担心编写 SQL 脚本,而可以专注于处理对象。如何使用它。Java还有一个伟大的东西,JPA(Java Persistence API),它实现了ORM概念。JPA 就是这样一个规范;它描述了对象的需求,它定义了使用数据库的各种接口和注释。JPA本质上是一种描述,一种标准。因此,具体的实现有很多,其中之一(也是最流行的之一)就是Hibernate,它是这个框架的精髓。Hibernate 是 JPA 规范的实现,旨在解决对象关系映射 (ORM) 问题。我们需要将这整个事情连接到我们的项目中。另外,为了让我们的 Spring 不袖手旁观,也参与到数据库的所有运动中,我们需要连接更多的模块,因为 我们从spring-webmvc依赖项中获得的所有内容都不足以满足此目的。我们还需要spring-jdbc来处理数据库,spring-tx来支持事务,以及spring-orm来处理 Hibernate。让我们添加依赖项pom.xml
:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.3.7.Final</version>
</dependency>
这两个依赖就足够了。javax.persistence-api
将与hibernate-core一起到达,spring-jdbc和spring-tx与spring-orm一起到达。
实体
所以我们希望类对象能够Film
存储在数据库中。为此,类必须满足许多条件。在 JPA 中有一个实体( Entity)。实体类是一个普通的POJO类,具有私有字段以及它们的 getter 和 setter。它必须有一个不带参数的非私有构造函数(或默认构造函数),并且必须有一个主键,即 唯一标识数据库中此类的每个记录的东西。您还可以单独阅读此类课程的所有要求。让我们Film
使用 JPA 注释使我们的类成为一个实体:
package testgroup.filmography.model;
import javax.persistence.*;
@Entity
@Table(name = "films")
public class Film {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "title")
private String title;
@Column(name = "year")
private int year;
@Column(name = "genre")
private String genre;
@Column(name = "watched")
private boolean watched;
// + getters and setters
}
@Entity
- 表明该类是一个实体。@Table
- 指向一个特定的表来显示该实体。@Id
— 表示该字段是主键,即 该属性将用于标识每个唯一的条目。@Column
— 将字段连接到表列。如果字段名和表列名相同,则可以省略。@GeneratedValue
— 该属性将自动生成;您可以在括号中指定如何生成。我们现在无法理解不同策略到底是如何运作的。知道在这种情况下每个新值都会比前一个值增加 1 就足够了。
休眠属性
好吧,让我们开始设置 Hibernate。首先,让我们将一些信息(例如用户名和密码、网址和其他内容)放入一个单独的文件中。当然,您可以直接在类中将它们指定为常规行,就像我们检查连接时所做的那样(String username = "root";
然后将其传递给创建连接的方法)。但将此类静态数据存储在某个文件中仍然更正确property
。例如,如果您需要更改数据库,那么您不必遍历所有类并查找使用它的位置;更改此文件中的值一次就足够了。让我们在资源目录中创建一个db.properties文件:
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=Europe/Minsk&useSSL=false
jdbc.username=root
jdbc.password=root
hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
hibernate.show_sql=true
好吧,从上面一切都清楚了,连接数据库的参数,即 驱动程序类名、url、用户名和密码。 hibernate.dialect
- 需要此属性来向 Hibernate 指示使用的是 SQL 语言的特定版本。事实上,在每个 DBMS 中,为了扩展功能、添加一些功能或优化某些内容,它们通常会对语言进行稍微现代化。结果是,每个 DBMS 都有自己的 SQL 方言。就像英语一样,看起来语言是一样的,但在澳大利亚、美国或英国会略有不同,有些单词可能有不同的含义。为了避免任何理解问题,您需要直接告诉 Hibernate 它到底要处理什么。 hibernate.show_sql
— 由于此属性,对数据库的查询将显示在控制台中。这不是必要的,但是有了这个东西你至少可以看看正在发生什么,否则看起来 Hibernate 正在做某种魔法。好吧,当然,它不会完全清晰地显示;最好使用某种记录器来实现这一点,但那是另一次的事情了,现在就可以了。
休眠配置
让我们继续设置配置。让我们在包中创建config
一个类HibernateConfig
:
package testgroup.filmography.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@ComponentScan(basePackages = " testgroup.filmography")
@EnableTransactionManagement
@PropertySource(value = "classpath:db.properties")
public class HibernateConfig {
private Environment environment;
@Autowired
public void setEnvironment(Environment environment) {
this.environment = environment;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
return properties;
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
return dataSource;
}
@Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan("testgroup.filmography.model");
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
@Bean
public HibernateTransactionManager transactionManager() {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
}
这里有很多新东西,所以最好在不同的来源中额外查找每个项目的信息。我们在这里简单回顾一下。
- 我们上课的时候
@Configuration
就已经想好了。@ComponentScan
WebConfig
@EnableTransactionManagement
— 允许您使用它TransactionManager
来管理交易。Hibernate 使用事务与数据库一起工作;它们需要作为一个整体来执行一组特定的操作,即 如果该方法中的任何一项操作出现问题,那么所有其他操作都不会被执行,这样就不会像经典的转账例子那样,当从一个账户提款的操作完成时,而是发生这种情况。写信给另一个人的操作不起作用,结果钱消失了。@PropertySource
— 连接我们最近创建的属性文件。Environment
- 为了从property
文件中获取属性。hibernateProperties
- 需要此方法将 Hibernate 属性表示为 Properties 对象DataSource
— 用于创建与数据库的连接。这是DriverManager的替代方案,我们之前在创建main
. 文档说DataSource
最好使用。当然,这就是我们要做的,不要忘记在互联网上阅读差异和优势是什么。一项特别的好处是能够创建数据库连接池 (DBCP)。sessionFactory
— 创建会话,借助该会话执行实体对象的操作。这里我们设置数据源、Hibernate 属性以及我们需要在哪个包中查找实体类。transactionManager
— 配置事务管理器。
DataSource
。文档说不DriverManagerDataSource
建议使用标准实现,即 ,因为 它只是普通连接池的替代品,通常只适用于测试等。对于普通应用程序,最好使用某种 DBCP 库。好吧,对于我们的应用程序来说,当然,我们拥有的已经足够了,但为了完成图片,也许我们仍然会按照建议使用另一个实现。让我们添加pom.xml
以下依赖项:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.10</version>
</dependency>
在dataSource
类方法中HibernateConfig
,我们将其替换DriverManagerDataSource
为BasicDataSource
包中的方法org.apache.tomcat.dbcp.dbcp2
:
BasicDataSource dataSource = new BasicDataSource();
好吧,一切似乎都准备好了,配置也准备好了,剩下的就是将其添加到我们的AppInitializer中:
protected Class<?>[] getRootConfigClasses() {
return new Class[]{HibernateConfig.class};
}
数据访问层
终于到了开始使用我们的 DAO 的时候了。我们去上课FilmDAOImpl
,首先我们从那里删除试用列表,我们不再需要它。让我们添加一个会话工厂并完成它。
private SessionFactory sessionFactory;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
首先,我们将创建一个方法来显示包含电影列表的页面,在其中我们将接收一个会话并向数据库发出请求(提取所有记录并形成一个列表):
public List<Film> allFilms() {
Session session = sessionFactory.getCurrentSession();
return session.createQuery("from Film").list();
}
这里有2点。首先,显示警告。这是因为我们想要接收一个参数化的List<Film>
,但该方法返回只是List
因为在编译时不知道请求将返回什么类型。所以这个想法警告我们,我们正在进行不安全的转换,这可能会导致麻烦。有几种更正确的方法可以做到这一点,这样就不会出现这样的问题。您可以在互联网上搜索信息。但现在我们不用担心这个。事实上,我们确切地知道将返回什么类型,因此这里不会出现问题,您可以简单地忽略该警告。但是,为了不碍眼,您可以在方法上方添加注释@SupressWarning("unchecked")
。通过这样做,我们有点告诉编译器,谢谢你,伙计,你的关心,但我知道我在做什么,一切都在掌控之中,所以你可以放松,不用担心这个方法。其次,这个想法用红色下划线“ from Film
”表示。这只是一个 HQL(Hibernate Query Language)查询,并且这个想法不知道是否一切都正确或有错误。您可以转到创意设置并手动调整所有内容(如果有兴趣,请在互联网上查找)。或者,您可以简单地添加对 Hibernate 框架的支持,为此,右键单击该项目,选择“添加框架支持” ,选中Hibernate复选框,然后单击“确定”。此后,很可能在实体类(Film
)中很多东西也会用红色下划线表示,例如注释中@Table(name = "films")
会发出警告Cannot parse table 'films'。再说一遍,这里没有任何问题,这不是设计错误,一切都会编译并工作。这个想法之所以被强调,是因为它对我们的基地一无所知。为了解决这个问题,让我们将这个想法与数据库集成起来。查看->工具窗口-> Persistense(将打开一个选项卡) ->鼠标右键,选择分配数据源->在数据源中,指定与数据库的连接,然后单击确定。 当这一切都解决之后,还剩下一些东西。让我们走向更高的层次,走向服务。在类中,FilmServiceImpl
我们allFilms
用注释标记 spring 方法@Transactional
,这将表明该方法应该在事务中执行(没有这个,Hibernate 将拒绝工作):
@Transactional
public List<Film> allFilms() {
return filmDAO.allFilms();
}
所以,这里一切都准备好了,你不需要碰控制器中的任何东西。好吧,看来关键时刻已经到来,单击“运行”,看看会发生什么。 这就是我们的标志,这次它不是从我们自己在课堂上列出的列表中获得的,而是从数据库中获得的。太好了,一切似乎都正常。现在我们使用会话方法以相同的方式执行所有其他 CRUD 操作。最终的类如下所示:
package testgroup.filmography.dao;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import testgroup.filmography.model.Film;
import java.util.List;
@Repository
public class FilmDAOImpl implements FilmDAO {
private SessionFactory sessionFactory;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
@SuppressWarnings("unchecked")
public List<Film> allFilms() {
Session session = sessionFactory.getCurrentSession();
return session.createQuery("from Film").list();
}
@Override
public void add(Film film) {
Session session = sessionFactory.getCurrentSession();
session.persist(film);
}
@Override
public void delete(Film film) {
Session session = sessionFactory.getCurrentSession();
session.delete(film);
}
@Override
public void edit(Film film) {
Session session = sessionFactory.getCurrentSession();
session.update(film);
}
@Override
public Film getById(int id) {
Session session = sessionFactory.getCurrentSession();
return session.get(Film.class, id);
}
}
现在剩下的就是记住转到服务并向方法添加注释@Transactional
。就这样,准备好了。您现在可以运行并检查。单击链接和按钮,尝试添加/删除/编辑条目。如果一切都正确完成,它应该可以工作。现在,这是一个使用 Hibernate、Spring、MySQL 的成熟 CRUD 应用程序。待续... 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