>
>
<!--其他plugin...........-->
<!--因为是类型安全的,所以还需要加上Maven APT plugin,使用 APT 自动生成一些类:-->
com.mysema.maven
apt-maven-plugin
1.1.3
generate-sources
process
target/generated-sources
com.querydsl.apt.jpa.JPAAnnotationProcessor
>
>
>
<!--SpringDataJPA-->
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-web
com.querydsl
querydsl-apt
provided
com.querydsl
querydsl-jpa
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
1.16.10
provided
>
server.port=8888
server.context-path=/
server.tomcat.uri-encoding=utf-8
#数据源配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot_test?characterEncoding=utf8
#数据库账号
spring.datasource.username=root
#数据库密码
spring.datasource.password=
spring.jpa.database=mysql
#是否展示sql
spring.jpa.show-sql=true
#是否自动生/更新成表,根据什么策略
spring.jpa.hibernate.ddl-auto=update
#命名策略,会将Java代码中的驼峰命名法映射到数据库中会变成下划线法
spring.jpa.hibernate.naming.strategy=org.hibernate.cfg.ImprovedNamingStrategy
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
//让Spring管理JPAQueryFactory
@Bean
public JPAQueryFactory jpaQueryFactory(EntityManager entityManager){
return new JPAQueryFactory(entityManager);
}
}
@Data
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer userId;
private String username;
private String password;
private String nickName;
private Date birthday;
private BigDecimal uIndex; //排序号
}
一般每有一个实体Bean配置了@Entity被检测到之后,就会在target的子目录中自动生成一个Q+实体名称 的类,这个类对我们使用QueryDSL非常重要,正是因为它,我们才使得QueryDSL能够构建类型安全的查询。
1.先简单了解@Query注解和@Modifying使用
1.nativeQuery=true则使用原生SQL默认HQL
2.如果没有,表示jpa使用实体类映射sql的方式执行语句
@Query(value = "select nextval(?1)", nativeQuery = true)
索引参数与命名参数
@Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")
List<Person> testQueryAnnotationParams1(String lastName, String email);
注释:上面代码中的?1,?2表示参数的占位符,需要和方法中所传递的参数顺序一致。
2、命名参数(推荐使用此方式):可以定义好参数名,赋值时使用@Param("参数名"),而不用管顺序。
// 为@Query注解传递参数的方式1:命名参数
@Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
List<Person> testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName);
@Modifying注解
1、在@Query注解中编写JPQL实现DELETE和UPDATE操作的时候必须加上@modifying注解,以通知Spring Data 这是一个DELETE或UPDATE操作。
2、UPDATE或者DELETE操作需要使用事务,此时需要 定义Service层,在Service层的方法上添加事务操作。
3、注意JPQL不支持INSERT操作。
@Transactional
@Modifying
@Query("UPDATE Person p SET p.email = :email WHERE p.id = :id")
void updatePersonEmail(@Param("id") Integer id, @Param("email") String email);
插入一个sql的语句的case—when函数的学习
select *,
case
gender
when 'MAN' then '男'
when 'WOMAN' then '女'
else '未知'
end as '性别'
from t_user;
Hibernate 注解@Column(nullable = false) 和 @Column(unique=true)
unique=true是指这个字段的值在这张表里不能重复,所有记录值都要唯一,就像主键那样;
nullable=false是这个字段在保存时必需有值,不能还是null值就调用save去保存入库;
使用JPA saveAndFlush()方法
实现 增加 和 修改
注意:
1.saveAndFlush 此方法先根据ID==null 判断是使用 persist方法 还是merge方法 之后
会根据数据库中ID是否有记录 来决定 是否要使用insert 还是 update
2.在saveAndFlush上,此命令中的更改将立即刷新到DB。使用save,就不一定了,它可能只暂时保留在内存中,
直到发出flush或commit命令。
3.但是要注意的是,即使在事务中刷新了更改并且未提交它们,这些更改对于外部事务仍然不可见,直到,提交这个事务。
4.在您的情况下,您可能使用某种事务机制,如果一切正常,它会为您发出commit命令。
主要作用用于,当你保存一条数据后又想马上拿到这条数据的id。
在repository包中添加xxxxRepository,使用QueryDSL时可以完全不依赖使用QueryDslPredicateExecutor,但是为了展示与SpringDataJPA的联合使用,我们让repository继承这个接口,以便获得支持:
1.继承JpaRepository<实体类,主键类型>
2.继承QueryDslPredicateExecutor<实体类>
#QueryDslPredicateExecutor接口
public interface QueryDslPredicateExecutor<T> {
T findOne(Predicate var1);
Iterable<T> findAll(Predicate var1);
Iterable<T> findAll(Predicate var1, Sort var2);
Iterable<T> findAll(Predicate var1, OrderSpecifier... var2);
Iterable<T> findAll(OrderSpecifier... var1);
Page<T> findAll(Predicate var1, Pageable var2);
long count(Predicate var1);
boolean exists(Predicate var1);
}
@Test
public void findUserLoginInfo(){
QLoginInfo loginInfo = QLoginInfo.loginInfo;
LoginInfo admin = jpaQueryFactory
.selectFrom(loginInfo)
.where(
loginInfo.userId.eq(1000),
loginInfo.userType.eq(SystemUserType.ADMIN)
).fetchOne();
System.out.println(admin);
}
@Test
public void findAll(){
QLoginInfo loginInfo = QLoginInfo.loginInfo;
List<LoginInfo> loginInfos = jpaQueryFactory.selectFrom(loginInfo).orderBy(loginInfo.id.desc()).fetch();
System.out.println(loginInfos.size());
System.out.println(loginInfos);
}
@Test
public void findAllPage(){
PageRequest of = PageRequest.of(0, 3);
QLoginInfo loginInfo = QLoginInfo.loginInfo;
QueryResults<LoginInfo> loginInfoQueryResults = jpaQueryFactory
.selectFrom(loginInfo)
.orderBy(loginInfo.id.asc())
.offset(of.getOffset())
.limit(of.getPageSize()).fetchResults();
List<LoginInfo> results = loginInfoQueryResults.getResults();
long total = loginInfoQueryResults.getTotal();
System.out.println("结果集:"+results);
System.out.println("结果条数:"+total);
}
/*展示dsl动态查询*/
@Test
public void findLonginInfo(){
QLoginInfo loginInfo = QLoginInfo.loginInfo;
/*可理解为获取一个构建动态条件拼装的对象*/
/*初始化一个这样的条件(logininfo0_.id is not null or logininfo0_.id is null)*/
Predicate predicate = loginInfo.isNotNull().or(loginInfo.isNull());
// predicate =
String loginPhone = "13800138000";
// String loginPhone = null;
int id = 1000;
/*
* 条件成立,进行条件的拼接
* */
predicate = loginPhone==null?predicate: ExpressionUtils.and(predicate,loginInfo.loginPhone.eq(loginPhone));
predicate = id == 0 ? predicate : ExpressionUtils.and(predicate,loginInfo.id.eq(id));
Iterable<LoginInfo> all = loginInfoRepository.findAll(predicate);
}
/**
* 动态条件排序、分组查询
* @param username
* @param password
* @param nickName
* @param birthday
* @param uIndex
* @return
*/
public List<User> findByUserPropertiesGroupByUIndex(String username, String password, String nickName, Date birthday, BigDecimal uIndex) {
QUser user = QUser.user;
//初始化组装条件(类似where 1=1)
Predicate predicate = user.isNotNull().or(user.isNull());
//执行动态条件拼装
predicate = username == null ? predicate : ExpressionUtils.and(predicate, user.username.eq(username));
predicate = password == null ? predicate : ExpressionUtils.and(predicate, user.password.eq(password));
predicate = nickName == null ? predicate : ExpressionUtils.and(predicate, user.nickName.eq(username));
predicate = birthday == null ? predicate : ExpressionUtils.and(predicate, user.birthday.eq(birthday));
predicate = uIndex == null ? predicate : ExpressionUtils.and(predicate, user.uIndex.eq(uIndex));
//执行拼装好的条件并根据userId排序,根据uIndex分组
List<User> list = jpaQueryFactory
.selectFrom(user)
.where(predicate) //执行条件
.orderBy(user.userId.asc()) //执行排序
.groupBy(user.uIndex) //执行分组
.having(user.uIndex.longValue().max().gt(7))//uIndex最大值小于7
.fetch();
//封装成Page返回
return list;
}
JPA使用@OneToOne来标注一对一的关系。
实体 People :用户。
实体 Address:家庭住址。
People 和 Address 是一对一的关系。
这里用两种方式描述JPA的一对一关系。
一种是通过外键的方式(一个实体通过外键关联到另一个实体的主键);
另外一种是通过一张关联表来保存两个实体一对一的关系。
1、通过外键的方式
people 表(id,name,sex,birthday,address_id)
address 表(id,phone,zipcode,address)
@Entity
public class People {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;//id
@Column(name = "name", nullable = true, length = 20)
private String name;//姓名
@Column(name = "sex", nullable = true, length = 1)
private String sex;//性别
@Column(name = "birthday", nullable = true)
private Timestamp birthday;//出生日期
@OneToOne(cascade=CascadeType.ALL)//People是关系的维护端,当删除 people,会级联删除 address
@JoinColumn(name = "address_id", referencedColumnName = "id")//people中的address_id字段参考address表中的id字段
private Address address;//地址
}
@Entity
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;//id
@Column(name = "phone", nullable = true, length = 11)
private String phone;//手机
@Column(name = "zipcode", nullable = true, length = 6)
private String zipcode;//邮政编码
@Column(name = "address", nullable = true, length = 100)
private String address;//地址
//如果不需要根据Address级联查询People,可以注释掉
// @OneToOne(mappedBy = "address", cascade = {CascadeType.MERGE, CascadeType.REFRESH}, optional = false)
// private People people;
}
实体 Author:作者。
实体 Article:文章。
Author 和 Article 是一对多关系(双向)。那么在JPA中,如何表示一对多的双向关联呢?
JPA使用@OneToMany和@ManyToOne来标识一对多的双向关联。一端(Author)使用@OneToMany,多端(Article)使用@ManyToOne。
在JPA规范中,一对多的双向关系由多端(Article)来维护。就是说多端(Article)为关系维护端,负责关系的增删改查。一端(Author)则为关系被维护端,不能维护关系。
一端(Author)使用@OneToMany注释的mappedBy="author"属性表明Author是关系被维护端。
多端(Article)使用@ManyToOne和@JoinColumn来注释属性 author,@ManyToOne表明Article是多端,@JoinColumn设置在article表中的关联字段(外键)。
@Entity
public class Author {
@Id // 主键
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
private Long id; //id
@NotEmpty(message = "姓名不能为空")
@Size(min=2, max=20)
@Column(nullable = false, length = 20)
private String name;//姓名
@OneToMany(mappedBy = "author",cascade=CascadeType.ALL,fetch=FetchType.LAZY)
//级联保存、更新、删除、刷新;延迟加载。当删除用户,会级联删除该用户的所有文章
//拥有mappedBy注解的实体类为关系被维护端
//mappedBy="author"中的author是Article中的author属性
private List<Article> articleList;//文章列表
}
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
@Column(name = "id", nullable = false)
private Long id;
@NotEmpty(message = "标题不能为空")
@Size(min = 2, max = 50)
@Column(nullable = false, length = 50) // 映射为字段,值不能为空
private String title;
@Lob // 大对象,映射 MySQL 的 Long Text 类型
@Basic(fetch = FetchType.LAZY) // 懒加载
@NotEmpty(message = "内容不能为空")
@Size(min = 2)
@Column(nullable = false) // 映射为字段,值不能为空
private String content;//文章全文内容
@ManyToOne(cascade={CascadeType.MERGE,CascadeType.REFRESH},optional=false)//可选属性optional=false,表示author不能为空。删除文章,不影响用户
@JoinColumn(name="author_id")//设置在article表中的关联字段(外键)
private Author author;//所属作者
}
最终生成的表结构
article 表(id,title,conten,author_id)
author 表(id,name)
实体 User:用户。
实体 Authority:权限。
用户和权限是多对多的关系。一个用户可以有多个权限,一个权限也可以被很多用户拥有。
JPA中使用@ManyToMany来注解多对多的关系,由一个关联表来维护。这个关联表的表名默认是:主表名+下划线+从表名。(主表是指关系维护端对应的表,从表指关系被维护端对应的表)。这个关联表只有两个外键字段,分别指向主表ID和从表ID。字段的名称默认为:主表名+下划线+主表中的主键列名,从表名+下划线+从表中的主键列名。
需要注意的:
1、多对多关系中一般不设置级联保存、级联删除、级联更新等操作。
2、可以随意指定一方为关系维护端,在这个例子中,我指定 User 为关系维护端,所以生成的关联表名称为: user_authority,关联表的字段为:user_id 和 authority_id。
3、多对多关系的绑定由关系维护端来完成,即由 User.setAuthorities(authorities) 来绑定多对多的关系。关系被维护端不能绑定关系,即Game不能绑定关系。
4、多对多关系的解除由关系维护端来完成,即由Player.getGames().remove(game)来解除多对多的关系。关系被维护端不能解除关系,即Game不能解除关系。
5、如果 User 和 Authority 已经绑定了多对多的关系,那么不能直接删除 Authority,需要由 User 解除关系后,才能删除 Authority。但是可以直接删除 User,因为 User 是关系维护端,删除 User 时,会先解除 User 和 Authority 的关系,再删除 Authority。
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotEmpty(message = "账号不能为空")
@Size(min=3, max=20)
@Column(nullable = false, length = 20, unique = true)
private String username; // 用户账号,用户登录时的唯一标识
@NotEmpty(message = "密码不能为空")
@Size(max=100)
@Column(length = 100)
private String password; // 登录时密码
@ManyToMany
@JoinTable(name = "user_authority",joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "authority_id"))
//1、关系维护端,负责多对多关系的绑定和解除
//2、@JoinTable注解的name属性指定关联表的名字,joinColumns指定外键的名字,关联到关系维护端(User)
//3、inverseJoinColumns指定外键的名字,要关联的关系被维护端(Authority)
//4、其实可以不使用@JoinTable注解,默认生成的关联表名称为主表表名+下划线+从表表名,
//即表名为user_authority
//关联到主表的外键名:主表名+下划线+主表中的主键列名,即user_id
//关联到从表的外键名:主表中用于关联的属性名+下划线+从表的主键列名,即authority_id
//主表就是关系维护端对应的表,从表就是关系被维护端对应的表
private List<Authority> authorityList;
}
@Entity
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(nullable = false)
private String name; //权限名
@ManyToMany(mappedBy = "authorityList")
private List<User> userList;
}
参考的博客:
https://blog.csdn.net/m0_37827482/article/details/84853076
https://blog.csdn.net/phapha1996/article/details/83614975
https://blog.csdn.net/johnf_nash/article/details/80642581?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task