使用JPA(Hibernate)操作数据库
JAP(Java Persisitence API),定义了对象关系映射(ORM)以及实体对象持久化的标准接口。
概述
在spring boot中JPA是依靠Hibernate才得以实现的。
JPA所维护的核心是实体(Entity Bean),而它是通过一个持久化上下文(Persinstence Context)来使用的。持久化上下文包含以下3个部分:
- 对象关系映射(Object Relational Mapping,简称ORM,或O/RM,或O/R映射)描述,JPA支持注解或者XML两种形式的描述,在spring boot中主要通过注解实现。
- 实体操作API,通过规范可以实现对实体对象的CRUD操作,来完成对象的持久化和查询。
- 查询语言,约定了面向对象的查询语言JPQL(Java Persistence Query Language),通过这层关系可以实现比较灵活的查询。
开发JPA
在Maven中引入spring-boot-starter-data-jpa
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
定义用户POJO
package com.lay.pojo;
//指明User是一个实体
@Entity(name = "user")
//定义映射的表
@Table(name = "t_user")
public class User {
//表明主键
@Id
//策略为递增
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id = null;
//定义属性和表的映射关系(属性和数据库列名不一致)
@Column(name = "user_name")
private String userName = null;
//定义转换器
@Convert(converter = SexConverter.class)
private SexEnum sex = null;//枚举
private String note = null;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public SexEnum getSex() {
return sex;
}
public void setSex(SexEnum sex) {
this.sex = sex;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
性别枚举类SexEnum
package com.lay.enumeration;
public enum SexEnum {
MALE(1, "男"), FEMALE(2, "女");
private int id;
private String name;
SexEnum(int id, String name) {
this.id = id;
this.name = name;
}
public static SexEnum getEnumById(int id) {
for (SexEnum sex : SexEnum.values()) {
if (sex.getId() == id) {
return sex;
}
}
return null;
}
public static SexEnum getEnumByName(String name) {
for (SexEnum sex : SexEnum.values()) {
if (sex.getName().equals(name)) {
return sex;
}
}
return null;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
性别转换器SexConverter
package com.lay.converter;
public class SexConverter implements AttributeConverter<SexEnum, Integer> {
//将枚举转换为数据库列
@Override
public Integer convertToDatabaseColumn(SexEnum sex) {
return sex.getId();
}
//將数据库列转换为枚举
@Override
public SexEnum convertToEntityAttribute(Integer id) {
return SexEnum.getEnumById(id);
}
}
JPA接口
有了上述POJO对象的定义,我们还需要一个JPA接口来定义对应的操作。为此Spring提供了JpaRepository接口,它本身也继承了其他接口。
JPA最顶级的接口是Repsitory,而它没有定义任何方法,定义方法的是它的子接口CrudRepository,其定义的最基本的增删改查操作,功能性还不足够强大。为此PagingAndSortingRepository则继承了它并且提供了分页和排序的功能,最后JpaRepository扩展了PagingAndSortingRepository,而且还有QueryByExampleExecutor接口,这样就可以拥有按例子(Example)查询的功能,一般而言。我们只需要定义JPA扩展接口JpaRepository便可以获得JPA提供的方法了。
JpaUserRepository
package com.lay.dao;
public interface JpaUserRepository extends JpaRepository<User, Long> {
}
这样便拥有了系统默认帮我们实现的方法。请注意,这并不需要提供任何实现类,这些spring会根据JPA接口规范帮我们完成。
接口扫描和实体注册
对于Jpa接口JpaUserRepository,还需要制定Spring boot的扫描路径,才能将接口扫描到spring ioc容器中。与此同时,我们还要将实体类User的注册给JPA才能测试这个控制器。用到两个JPA注解:
- @EnableJpaRepositories:启用JPA编程
- @EntityScan:对实体Bean扫描
启动主函数SpringbootDatabaseApplication
package com.lay;
@SpringBootApplication
//定义JPA接口扫描包路径
@EnableJpaRepositories(basePackages = "com.lay.dao")
//定义实体Bean扫描包路径
@EntityScan(basePackages = "com.lay.pojo")
public class SpringbootDatabaseApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDatabaseApplication.class, args);
}
}
实际上,即使没有使用@EnableJpaRepositories和@EntityScan,只要依赖了spring-boot-starter-jpa。spring boot2.x也会对项目进行扫描。
配置文件
application.properties
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot_database
spring.datasource.username=root
spring.datasource.password=123456
# spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#指定数据连接池的类型
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
#最大等待连接中的数量,设置0为没有限
spring.datasource.dbcp2.max-idle=10
#最大连接活动数
spring.datasource.dbcp2.max-total=50
#最大等待毫秒数,单位ms,超过时间会出错误信息
spring.datasource.dbcp2.max-wait-millis=10000
#数据库连接池初始化连接数
spring.datasource.dbcp2.initial-size=5
#使用MySQL数据库方言
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
#打印数据库sql
spring.jpa.show-sql=true
#选择hibernate数据定义语言(DDL)策略为update
spring.jpa.hibernate.ddl-auto=update
控制器测试接口
JpaController
package com.lay.controller;
@Controller
@RequestMapping(value = "/jpa")
public class JpaController {
//注入JPA接口
@Autowired
private JpaUserRepository jpaUserRepository = null;
@RequestMapping("/getUser")
@ResponseBody
public User getUser(Long id) {
//使用JPA接口查询对象
User user = jpaUserRepository.findById(id).get();
return user;
}
}
启动服务测试,在浏览器地址栏输入http://localhost:8080/jpa/getUser?id=1
日志打印了sql
Hibernate: select user0_.id as id1_0_0_, user0_.note as note2_0_0_, user0_.sex as sex3_0_0_, user0_.user_name as user_nam4_0_0_ from t_user user0_ where user0_.id=?
使用JPA查询语言JPQL
JPA查询语言JPQL与Hibernate提供的HQL是十分接近的。这里使用注解@Query标识语句就可以了。
JpaUserRepository
package com.lay.dao;
public interface JpaUserRepository extends JpaRepository<User, Long> {
@Query("form user where user_name like concat('%',?1,'%')" + "and note like concat('%',?2,'%')")
public List<User> findUsers(String userName, String note);
}
注意这里写from user中的user是定义实体类名称(@Entity注解的name属性),所以才能这样定义一条JPQL,提供给上层调用。
JPA命名查询
除了可以定义语句查询,按照一定规则命名的方法也可以在不写任何代码的情况下完成逻辑。
package com.lay.dao;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.lay.pojo.User;
public interface JpaUserRepository extends JpaRepository<User, Long> {
@Query("form user where user_name like concat('%',?1,'%')" + "and note like concat('%',?2,'%')")
public List<User> findUsers(String userName, String note);
/**
* 按用户名称模糊查询
* @param userName
* @return
* @Date 2018年10月29日 下午4:58:07
* @Author lay
*/
List<User> findByUserNameLike(String userName);
/**
* 根据主键查询
* @param id
* @return
* @Date 2018年10月29日 下午4:58:45
* @Author lay
*/
User getUserById(Long id);
/**
* 按照用户名称或者备注进行模糊查询
* @param userName
* @param note
* @return
* @Date 2018年10月29日 下午4:59:57
* @Author lay
*/
List<User> findByUserNameLikeOrNoteLike(String userName, String note);
}
这里的命名是以动词(get/find)开始的,而已by代表按照什么内容进行查询。