一.Springboot下JPA根据实体类自动建表(Demo使用mysql数据库,alibaba线程池)
1.Maven中引入相应依赖
<!-- JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!-- 阿里巴巴连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.4</version>
</dependency>
2.配置application.yml
ddl-auto可选参数 :
create 启动时删数据库中的表,然后创建,退出时不删除数据表
create-drop 启动时删数据库中的表,然后创建,退出时删除数据表 如果表不存在报错
update 如果启动时表格式不一致则更新表,原有数据保留
validate 项目启动表结构进行校验 如果不一致则报错
spring:
##数据源
datasource:
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8
username: root
password: 123qwe
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
jpa:
hibernate:
ddl-auto: update
show-sql: true
3.配置建表对应的实体类
@Data
: lombok插件的注解,为类提供读写属性, 此外还提供了 equals()、hashCode()、toString() 方法
@Id
: 设置number为主键
@GeneratedValue(strategy = GenerationType.IDENTITY)
:设置主键自动增长
/**
* @Description 选手实体
* @Author 张小黑的猫
* @data 2019-03-29 17:23
*/
@Entity
@Data
@Table(name = "player")
public class Player {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
/**
* 选手编号
*/
private Integer number;
private String name;
private String gender;
private Integer age;
private String idCard;
private String phone;
private String school;
private String speciality;
private String worksName;
private String worksType;
private LocalDateTime worksTime;
}
4.运行Springboot项目,查看数据库,对应数据表已经生成。
控制台打印的建表语句:
2019-03-30 17:16:57.233 INFO 19452 --- [ restartedMain] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect
Hibernate: create table player (number integer not null auto_increment, age integer, gender varchar(255), id_card varchar(255), name varchar(255), phone varchar(255), school varchar(255), speciality varchar(255), works_name varchar(255), works_time datetime, works_type varchar(255), primary key (number)) engine=MyISAM
数据库中生成的表:
二:使用JPA提供的基础CRUD操作
1.继承JPA提供的JpaRepository<T, ID>接口
/**
* @Description
* @Author 张小黑的猫
* @data 2019-03-30 10:28
*/
@Repository
public interface PlayerRepository extends JpaRepository<Player,Integer> {
}
JpaRepository<T, ID>接口提供了基本的crud操作,可以直接调用
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAllById(Iterable<ID> var1);
<S extends T> List<S> saveAll(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
2.编写cotroller、service调用响应的方法,进行crud操作
controller层代码:
@GetMapping("/player/{number}")
@ResponseBody
public Player getPlayerInformation(@PathVariable Integer number){
Optional<Player> player = playerService.selectPlayer(number);
if(player.isPresent()){
return player.get();
}else{
return null;
}
}
@GetMapping("/player")
@ResponseBody
public List<Player> getPlayerInformation(){
return playerService.selectPlayer();
}
@PostMapping("/player")
@ResponseBody
public Player addPlayerInformation(Player player){
return playerService.insertPlayer(player);
}
@PutMapping("/player")
@ResponseBody
public Player updatePlayerInformation(Player player){
return playerService.updatePlayer(player);
}
@DeleteMapping("/player/{number}")
@ResponseBody
public List<Player> deletePlayerInformation(@PathVariable Integer number){
playerService.deletePlayer(number);
return playerService.selectPlayer();
}
Service层代码:
/**
* @Description
* @Author 张小黑的猫
* @data 2019-03-30 10:33
*/
public interface PlayerService {
Player insertPlayer(Player player);
void deletePlayer(Integer number);
Player updatePlayer(Player player);
List<Player> selectPlayer();
Optional<Player> selectPlayer(Integer number);
}
/**
* @Description
* @Author 张小黑的猫
* @data 2019-03-30 10:42
*/
@Service
public class PlayerServiceImpl implements PlayerService {
@Autowired
private PlayerRepository playerRepository;
@Override
public Player insertPlayer(Player player) {
return playerRepository.save(player);
}
@Override
public void deletePlayer(Integer number) {
playerRepository.deleteById(number);
}
@Override
public Player updatePlayer(Player player) {
return playerRepository.save(player);
}
@Override
public List<Player> selectPlayer() {
return playerRepository.findAll();
}
@Override
public Optional<Player> selectPlayer(Integer number) {
return playerRepository.findById(number);
}
}
已在数据表中添加数据如图:
用PostMan进行接口测试:
① [get获取数据列表]
localhost:8092/player
测试结果:② [get获取指定id的数据]
localhost:8092/player/19010
测试结果:③ [post添加一条数据]
localhost:8092/player?name=test&age=20
测试结果:④ [put更新一条数据]
localhost:8092/player?name=update&age=10&number=19018&school=家里蹲
⑤ [delete删除一条数据]
localhost:8092/player/19018
测试结果:三、构建动态条件查询
JPA是支持动态查询的,需要我们的repository 继承JpaSpecificationExecutor接口,使用的时候传入相应参数即可。先看一下JpaSpecificationExecutor接口:
public interface JpaSpecificationExecutor<T> {
Optional<T> findOne(@Nullable Specification<T> var1);
List<T> findAll(@Nullable Specification<T> var1);
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
long count(@Nullable Specification<T> var1);
}
可以看到JpaSpecificationExecutor提供了五个方法,它们都必须传入的参数Specification<T> var1就是我们用来构建动态条件查询的关键。 其中,Pageable是分页查询用的,Sort是排序用的,这里先介绍Specification的使用。
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
这里的参数Root<T> root
负责实体类与数据表的映射 ,CriteriaBuilder cb
负责构建查询语句, riteriaQuery<?> query
负责执行查询语句,具体使用如下:
1.继承JpaSpecificationExecutor接口
@Repository
public interface PlayerRepository extends JpaRepository<Player,Integer>, JpaSpecificationExecutor {
}
2.Service层加入动态条件查询方法的实现
service:
List<Player> selectPlayer(Player player);
serviceImpl:
@Override
public List<Player> selectPlayer(Player player) {
Specification<Player> query = new Specification<Player>() {
@Override
public Predicate toPredicate(Root<Player> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if(player.getNumber()!=null){
predicates.add(criteriaBuilder.equal(root.get("number"),player.getNumber()));
}
if(StringUtils.isNotEmpty(player.getGender())){
predicates.add(criteriaBuilder.equal(root.get("gender"),player.getGender()));
}
if(StringUtils.isNotEmpty(player.getName())){
predicates.add(criteriaBuilder.like(root.get("name"),"%"+player.getName()+"%"));
}
if(StringUtils.isNotEmpty(player.getIdCard())){
predicates.add(criteriaBuilder.like(root.get("idCard"),"%"+player.getIdCard()+"%"));
}
if(StringUtils.isNotEmpty(player.getWorksName())){
predicates.add(criteriaBuilder.like(root.get("worksName"),"%"+player.getWorksName()+"%"));
}
if(StringUtils.isNotEmpty(player.getWorksType())){
predicates.add(criteriaBuilder.equal(root.get("worksType"),player.getWorksType()));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
return playerRepository.findAll(query);
}
其中Root是查询的根对象,CriteriaBuilder可以用来构建查询关系。
以predicates.add(criteriaBuilder.equal(root.get("number"),player.getNumber()))
为例,
"number"
指定了实体Player的属性number,通过root.get("number")
获取实体属性number对应库表中的字段名称,player.getNumber()
获取当前传入的查询条件值,equal指的是等于,criteriaBuilder将他们构建为对应的sql语句。此段代码的含义是where条件,如:number='19001'。(指定的属性名必须与查询实体的属性名一致,否则会报错。)相同的道理,predicates.add(criteriaBuilder.like(root.get("name"),"%"+player.getName()+"%"))
即为模糊匹配。
最后return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]))
返回Specification的实例,然后作为参数传入到playerRepository.findAll(query)
中执行拼接好的动态条件查询。
当然criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]))
取的and关系,还可以使用or,道理都一样。
3.Controller调用方法,用PostMan测试
controller:
@GetMapping("/player")
@ResponseBody
public List<Player> getPlayerInformation(Player player){
return playerService.selectPlayer(player);
}
测试
①localhost:8092/player?number=19003
②
localhost:8092/player?idCard=1998
③
localhost:8092/player?idCard=1998&gender=男
四、构建排序和分页查询
我们在上面的动态分页查询的基础上增加分页查询的功能,先放更改后的代码。
@Override
public List<Player> selectPlayer(Player player, Integer page, Integer size) {
Sort.Direction sort = Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page-1, size, sort, "number");
Specification<Player> query = new Specification<Player>() {
@Override
public Predicate toPredicate(Root<Player> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if(player.getNumber()!=null){
predicates.add(criteriaBuilder.equal(root.get("number"),player.getNumber()));
}
if(StringUtils.isNotEmpty(player.getName())){
predicates.add(criteriaBuilder.like(root.get("name"),"%"+player.getName()+"%"));
}
if(StringUtils.isNotEmpty(player.getIdCard())){
predicates.add(criteriaBuilder.like(root.get("idCard"),"%"+player.getIdCard()+"%"));
}
if(StringUtils.isNotEmpty(player.getWorksName())){
predicates.add(criteriaBuilder.like(root.get("worksName"),"%"+player.getWorksName()+"%"));
}
if(StringUtils.isNotEmpty(player.getWorksType())){
predicates.add(criteriaBuilder.equal(root.get("worksType"),player.getWorksType()));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
return playerRepository.findAll(query,pageable).getContent();
}
我们使用的是JpaSpecificationExecutor提供的方法:
public interface JpaSpecificationExecutor<T> {
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
}
Sort.Direction
是个枚举有ASC(升序)和DESC(降序),Pageable pageable = PageRequest.of(page-1, size, sort, "number");
获取PageRequest对象,page页码从0开始,size页容量,sort排序方式,以number为准进行排序。
Page接口如下:
public interface Page<T> extends Iterable<T> {
int getNumber(); //当前第几页 返回当前页的数目。总是非负的
int getSize(); //返回当前页面的大小。
int getTotalPages(); //返回分页总数。
int getNumberOfElements(); //返回当前页上的元素数。
long getTotalElements(); //返回元素总数。
boolean hasPreviousPage(); //返回如果有上一页。
boolean isFirstPage(); //返回当前页是否为第一页。
boolean hasNextPage(); //返回如果有下一页。
boolean isLastPage(); //返回当前页是否为最后一页。
Iterator<T> iterator();
List<T> getContent(); //将所有数据返回为List
boolean hasContent(); //返回数据是否有内容。
Sort getSort(); //返回页的排序参数。
}
五、使用@Query使用自定义的SQL语句
使用@Query
注解,我们需要先了解它的参数:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@QueryAnnotation
@Documented
public @interface Query {
String value() default "";
String countQuery() default "";
String countProjection() default "";
boolean nativeQuery() default false;
String name() default "";
String countName() default "";
}
其中:
value
指定JPQL语句,当nativeQuery=true
时是原生的sql语句
countQuery
指定count的JPQL语句,不指定则自动生成,当nativeQuery=true
时是原生的sql语句,countQuery
主要用来配合 value
实现分页功能
countProjection
依据哪个字段来count一般默认即可
nativeQuery
默认是false,表示value 里面是不是原生的Sql 语句
name
指定一个query 的名字,必须是唯一的。 如果不指定,默认的生成规则是:{$domainClass}.${queryMethodName}
countName
指定一个count 的query 名字,必须是唯一的。如果不指定,默认的生成规则是:{$domainClass}.${queryMethodName}.count
这里主要介绍常用的value
和nativeQuery
的使用。
1.一个使用@Query的简单例子(?n
引入参数,参数顺序不可调换)
@Query(value = "select p from Player p where p.worksType = ?1 and p.age <= ?2 and p.gender = ?3")
List<Player> findByCondition(String worksType, Integer age, String gender);
@Query(value = "select p.* from player p where p.works_type = ?1 and p.age <= ?2 and p.gender = ?3",nativeQuery = true)
List<Player> findByCondition(String worksType, Integer age, String gender);
以上是@Query
的两种写法
第一种nativeQuery = false
,使用基于实体的查询方式,from后面跟的是实体名,where后面的p.worksType
对应的实体的字段;
第二种nativeQuery = true
,使用基于数据表的查询方式,from后面跟的是表名,where后面的p.works_type
对应的数据表中的列名;
2.Like表达式使用 (@Param
注解注入参数,:
引入参数,需要指定参数名)
@Query(value = "select p from Player p where p.worksName like %:worksName% and p.gender = :gender")
List<Player> findByCondition(@Param("worksName") String worksName, @Param("gender")String gender);
3.修改语句的自定义使用
@Query
和@Modifying
同时使用用来自定义数据更改操作的SQL,@Transactional
负责提交事务
@Transactional
@Query(value = "update Player p set p.worksName = '拍摄的照片' where p.worksType =?1")
@Modifying
Integer updateByCondition(String worksType);
六、自定义返回实体
我们这里示例返回一个自定义的实体类Student
1.定义需要返回的实体(需要getset方法和全参构造器,这里使用lombok的注解实现);
@Data
@AllArgsConstructor
public class Student {
private String name;
private String sex;
private Integer age;
private String school;
private String speciality;
}
2.编写Repository
/**
* 返回自定义实体
* @return
*/
@Query(value = "select new com.example.bootstraptable.entity.Student(p.name,p.gender,p.age,p.school,p.speciality) from Player p")
List<Student> getStudents();
使用基于实体的查询来实现返回自定义实体
其中new com.example.bootstraptable.entity.Student(p.name,p.gender,p.age,p.school,p.speciality)
为Student
的全参构造函数,使用com.example.bootstraptable.entity.Student
引入实体类,相较于直接new Student()
,使用包名引入更能准确的找到对应的返回实体。
3.调用,测试返回结果