持久层技术,简单来说,就是通过操纵对象来操作数据库。(具体见前言部分)
本期博客继续深入Spring Boot技术,值得一提的是,尽管Spring Boot操作起来还是不错的,看都看明白,但还是强烈建议大家自己动手操作,里面的坑还是很多的,只有经历了自己才知道。一般来说,像Web项目不会精确定位是哪一段代码有什么问题,只会告诉你有一些逻辑问题,大多我在Java里面遇到的就是NULL空指针类型的,寻找错误起来异常麻烦,很多时候都是一个细节需要找很久。(所以真的,Code必须要仔细啊)
本期的数据库操作主要围绕着JdbcTemplate ,MyBatis,Spring Data JPA,不讲JDBC了(第一个也都是使用数据连接池来操作的)
后期的博客主要是整合NoSQL,构建RESTful服务,单元测试,安全管理等,持续更新!(不过我主要还是以后端为主,前端能力依旧差很多,需要在短期补足一下)
前言
持久层,可以理解成数据 保存在 数据库或者 硬盘一类可以保存很长时间的设备里面,不像放在内存中那样断电就消失了,也就是把数据存在持久化设备上。
持久化可以通过很多方式,写文件和数据库都可以。只是现在企业一般都会选择把数据持久化到数据库中,因为可以很方便的查询统计分析,但数据库的数据最终还是会写到磁盘上的。Java 程序员为了操作数据库, 最开始是使用JDBC* 来进行的,但是这种方式* 开发效率低 ,要写一堆重复代码,加上关系数据库和对象本身存在所谓的阻抗不匹配情况,所以 为了提高开发效率,有人发明了 ORM 即 对象关系映射框架* (Hibernate是其中的佼佼者),对于 Java 程序员来说,就可以通过操纵对象来操纵数据库了。
持久层是Java EE中访问数据库的核心操作,Spring Boot对常见的持久层框架提供了自动化配置,例如JdbcTemplate,JPA等,MyBatis的自动化配置是MyBatis官方提供的。
其中,JdbcTemplate使用得不是很广泛,MyBatis灵活性较好,方便开发者进行SQL优化。Spring Data JPA使用方便,能快速实现一共RESTful风格的应用。
一般构建目录如下:分为Controller,dao,model,service层相互应用。
整合JdbcTemplate
JdbcTemplate是Spring提供的JDBC模板框架,利用AOP技术来解决使用JDBC时大量重复代码问题。虽没有MyBatis那么灵活但是方便很多。
这里都是以数据连接池为例。
下面以一个实例为例:
建表命令:
create database chapter05 default character set utf8;
use chapter05;
create table book(
**id int(11) not null auto_increment,**
name varchar(128) default null,
author varchar(64) default null,
primary key (id)
);
insert into book (id, name, author) value (1,'ABC','ACD');
添加依赖:
spring-boot-jdbc提供了spring-jdbc ,例外加入了数据库驱动依赖和数据库连接池依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.2</version>
</dependency>
数据库配置:
在application.properties配置数据库连接信息:记得配置完依赖需要刷新依赖。
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql:///chapter05
spring.datasource.username = root
spring.datasource.password = qazwsx123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver // 下面版本号为8.0.30
创建book的实体:
public class Book {
private Integer id;
private String name;
private String author;
@JsonFormat(pattern = "yyyy-MM-dd")
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public String getAuthor() {
return author;
}
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAuthor(String author) {
this.author = author;
}
}
创建BookDao,创建数据库访问层:
@Repository
public class BookDao {
@Autowired(
required = false
)
JdbcTemplate jdbcTemplate;
public int addBook(Book book) {
return jdbcTemplate.update("insert into book(name ,author) values (?,?)",book.getName(),book.getAuthor());
}
public int updateBook(Book book) {
return jdbcTemplate.update("update book set name=?,author=? where id=?",book.getName(),book.getAuthor(),book.getId());
}
public int deleteBookById(Integer id) {
return jdbcTemplate.update("delete from book where id =? ",id);
}
public Book getBookById(Integer id) {
return jdbcTemplate.queryForObject("select * from book where id = ?",new BeanPropertyRowMapper<>(Book.class),id);
}
public List<Book> getAllBooks() {
return jdbcTemplate.query("select * from book",new BeanPropertyRowMapper<>(Book.class));
}
}
在我没有对AUTOWIRED进行配置的时候,出现了这个错误:Could not autowire. No beans of ‘JdbcTemplate’ type found.
所以需要加上false语句来降低安全性。
解释:
注入JdbcTemplate,由于添加了spring-jdbc相关依赖,所以会自动注册到Spring容器,因此可以直接注入JdbcTemplate使用。
增删改三种类型操作主要使用update,batchUpdate来完成。query和queryForObject主要完成查询功能。还有execute来执行任意的SQL,call方法。
在执行查询操作,需要一个RowMapper将查询的列和实体相对应。如果列名和属性名相同,那么可以直接使用BeanPropertyRowMapper,如果列名和属性不同,就需要开发者自己实现RowMapper接口。
创建Service
@Service
public class BookService {
@Autowired(
required = false
)
BookDao bookDao;
public int addBook(Book book) {
System.out.println(book.getId());
System.out.println(book.getAuthor());
System.out.println(book.getName()+"1233");
return bookDao.addBook(book);
}
public int updateBook(Book book) {
return bookDao.updateBook(book);
}
public int deleteBookById(Integer id) {
return bookDao.deleteBookById(id);
}
public Book getBookById(Integer id) {
return bookDao.getBookById(id);
}
public List<Book> getAllBooks() {
return bookDao.getAllBooks();
}
}
Controller:
@RestController
public class BookController {
@Autowired
BookService bookService;
@GetMapping("/bookOps")
public void bookOps() {
Book b1 = new Book();
b1.setId(4);
b1.setName("西厢记");
b1.setAuthor("王实甫");
int i = bookService.addBook(b1);
System.out.println("addBook>>>" + i);
Book b2 = new Book();
b2.setId(1);
b2.setName("朝花夕拾");
b2.setAuthor("鲁迅");
int updateBook = bookService.updateBook(b2);
System.out.println("updateBook>>>"+updateBook);
Book b3 = bookService.getBookById(1);
System.out.println("getBookById>>>"+b3);
int delete = bookService.deleteBookById(2);
System.out.println("deleteBookById>>>"+delete);
List<Book> allBooks = bookService.getAllBooks();
System.out.println("getAllBooks>>>"+allBooks);
}
}
在这里我遇到了很多坑,首先是初始化问题。记住一定要把类的创建放在autowired(自动装箱)后面,否则一直会报错null.
整合MyBatis
MyBatis是一款优秀的持久层框架,支持定制化SQL,存储过程以及高级映射。MyBatis几乎避免所有的JDBC代码手动设置参数以及获取结果集。在传统的SSM框架整合中,使用它需要大量的配置,而在Spring Boot提供了自动化配置方案,可以做到开箱即用。步骤如下:
首先展现项目文件目录情况:
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
数据库和表,实体类以及application.properties配置见上文jdbcTemplate一节。
创建数据库访问层
BookMapper:
@Mapper
public interface BookMapper {
int addBook(Book book);
int deleteBookById(Integer id);
int updateBookById(Book book);
Book getBookById(Integer id);
List<Book> getAllBooks();
}
@Mapper注解,表示这是MyBatis中的接口,这种方式在每个Mapper添加注解。
BookMapper.xml:
BookMapper接口中的每一个方法都在BookMapper.xml实现了。
#{}用来替代接口中的参数,实体类中的属性可以直接通过#{实体类属性名}获取。
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace ="com.example.demo.testMybatis.mapper.BookMapper">
<insert id ="addBook" parameterType="com.example.demo.testMybatis.model.Book">
insert into book (name,author) values (#{name},#{author})
</insert>
<delete id="deleteBookById" parameterType="int">
delete from book where id = #{id}
</delete>
<update id="updateBookById" parameterType="com.example.demo.testMybatis.model.Book">
update book set name = #{name},author = #{author} where id = #{id}
</update>
<select id="getBookById" parameterType="int" resultType="com.example.demo.testMybatis.model.Book">
select * from book where id = #{id}
</select>
<select id="getAllBooks" resultType="com.example.demo.testMybatis.model.Book">
select * from book
</select>
</mapper>
BookService.java:
@Service
public class BookService {
@Autowired(
required = false
)
BookMapper bookMapper;
public int addBook(Book book) {
return bookMapper.addBook(book);
}
public int updateBook(Book book) {
return bookMapper.updateBookById(book);
}
public int deleteBookById(Integer id) {
return bookMapper.deleteBookById(id);
}
public Book getBookById(Integer id) {
return bookMapper.getBookById(id);
}
public List<Book> getAllBooks() {
return bookMapper.getAllBooks();
}
}
BookController.java:
@RestController
public class BookController {
@Autowired
BookService bookService;
@GetMapping("/bookOps")
public void bookOps() {
Book b1 = new Book();
b1.setName("西厢记");
b1.setAuthor("王实甫");
int i = bookService.addBook(b1);
System.out.println("addBook>>>" + i);
Book b2 = new Book();
b2.setId(1);
b2.setName("朝花夕拾");
b2.setAuthor("鲁迅");
int updateBook = bookService.updateBook(b2);
System.out.println("updateBook>>>"+updateBook);
Book b3 = bookService.getBookById(1);
System.out.println("getBookById>>>"+b3);
int delete = bookService.deleteBookById(2);
System.out.println("deleteBookById>>>"+delete);
List<Book> allBooks = bookService.getAllBooks();
System.out.println("getAllBooks>>>"+allBooks);
}
}
配置pom.xml文件:
在Maven工程中,xml配置文件建议写在resources目录,但我们的xml是写在了Mapper包下,故需要指明资源文件位置:
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
当然可以把xml放在resources包中,并把文件夹名改为mapper,然后在application.properties配置:
# 配置mybatis
mybatis.mapper-locations=classpath:/mapper/*.xml
输入8080/bookOps,便可以在控制台打印出日志:
整合Spring Data JPA
JPA是一种ORM规范。Spring Data 是Spring的一个子项目,致力于简化数据库访问。通过规范的方法名称来分析开发者的意图,进而减少数据库访问层的代码量。Spring Data 不仅支持关系型数据库,也支持非关系型数据库。
Spring Boot 整合Spring Data JPA的步骤如下:
先建一个数据库:
create database jpa default character set utf8;
创建数据库即可不用建表。
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
数据库配置:在application.properties配置数据库基本信息
1-4行是数据库基本信息配置,5-8行是JPA相关配置。第5行表示是否在控制台打印生成的SQL。第6行表示对JPA对应的数据库为MySQL,第7行表示项目启动时实时更新数据库中的表。第8行表示使用的数据库方言。
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql:///jpa
spring.datasource.username=root
spring.datasource.password=qazwsx123
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
创建实体:
Book.java:
import javax.persistence.*;
@Entity(name ="jpabook")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "book_name",nullable = false)
private String name;
private String author;
private Float price;
@Transient
private String description;
//省略getter/setter
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
", description='" + description + '\'' +
'}';
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
}
@Entity注解表示该类是一个实体类,项目启动时生成一张表,表的名称就是name值。(如果不配置name,默认为类名)
所有的实体类都要有主键,@Id表示该属性是一个主键,@GeneratedValue表示主键自动生成,strategy表示主键的生成策略。
默认情况下,生成的表字段名称就是实体类的属性。
@Transient注解表示在生成数据库表的时候不生成对应的字段。
创建BookDao接口:
BookDao.java:
public interface BookDao extends JpaRepository<Book,Integer> {
List<Book> getBooksByAuthorStartingWith(String author);
List<Book> getBooksByPriceGreaterThan(Float price);
@Query(value = "select * from t_book where id=(select max(id) from t_book)",nativeQuery = true)
Book getMaxIdBook();
@Query("select b from book b where b.id>:id and b.author=:author")
List<Book> getBookByIdAndAuthor(@Param("author") String author, @Param("id") Integer id);
@Query("select b from book b where b.id<?2 and b.name like %?1%")
List<Book> getBooksByIdAndName(String name, Integer id);
}
自定义BookDao继承自JpaRepository.其提供了一些基本的数据操作方法,有基本的增删改查、分页查询、排序查询等。
在JPA中,只要方法的定义符合既定规范,Spring Data就能分析出开发者的意图,从而避免开发者定义SQL。(既定规范,就是一定的方法命名规则,支持的命名规则如下:)
创建BookService:
@Service
public class BookService {
@Autowired
BookDao bookDao;
public void addBook(Book book) {
bookDao.save(book);
}
public Page<Book> getBookByPage(Pageable pageable) {
return bookDao.findAll(pageable);
}
public List<Book> getBooksByAuthorStartingWith(String author){
return bookDao.getBooksByAuthorStartingWith(author);
}
public List<Book> getBooksByPriceGreaterThan(Float price){
return bookDao.getBooksByPriceGreaterThan(price);
}
public Book getMaxIdBook(){
return bookDao.getMaxIdBook();
}
public List<Book> getBookByIdAndAuthor(String author, Integer id){
return bookDao.getBookByIdAndAuthor(author, id);
}
public List<Book> getBooksByIdAndName(String name, Integer id){
return bookDao.getBooksByIdAndName(name, id);
}
}
在addBook中save方法将对象数据保存到数据库,save方法是由JpaRepository接口提供的。
在Page 是一个分页查询,使用findAll。
创建BookController,实现对数据的测试:
@RestController
public class BookController {
@Autowired
BookService bookService;
@GetMapping("/findAll")
public void findAll() {
PageRequest pageable = PageRequest.of(2, 3);
Page<Book> page = bookService.getBookByPage(pageable);
System.out.println("总页数:"+page.getTotalPages());
System.out.println("总记录数:"+page.getTotalElements());
System.out.println("查询结果:"+page.getContent());
System.out.println("当前页数:"+(page.getNumber()+1));
System.out.println("当前页记录数:"+page.getNumberOfElements());
System.out.println("每页记录数:"+page.getSize());
}
@GetMapping("/search")
public void search() {
List<Book> bs1 = bookService.getBookByIdAndAuthor("鲁迅", 7);
List<Book> bs2 = bookService.getBooksByAuthorStartingWith("吴");
List<Book> bs3 = bookService.getBooksByIdAndName("西", 8);
List<Book> bs4 = bookService.getBooksByPriceGreaterThan(30F);
Book b = bookService.getMaxIdBook();
System.out.println("bs1:"+bs1);
System.out.println("bs2:"+bs2);
System.out.println("bs3:"+bs3);
System.out.println("bs4:"+bs4);
System.out.println("b:"+b);
}
@GetMapping("/save")
public void save() {
Book book = new Book();
book.setAuthor("鲁迅");
book.setName("呐喊");
book.setPrice(23F);
bookService.addBook(book);
}
}
调用8080/save,数据库就用显示相应的数据,同时使用8080/search查看数据。
多数据源
所谓多数据源,就是JavaEE项目中采用了不同数据库实例中的多个库,或者同一个数据库实例中多个不同的库。一般来说,采用MyCat等分布式数据库中间件是比较好的解决方案,这样可以把数据库读写分离,分库分表,备份等操作。
对于以上的操作不再讲述多数据源操作方法。