SpringDataJPA+QueryDSL

QueryDSL JPA的使用

编写配置
pom.xml配置
<build>
        <plugins>
            <!--其他plugin...........-->
 
            <!--因为是类型安全的,所以还需要加上Maven APT plugin,使用 APT 自动生成一些类:-->
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
dependencies中配置以下依赖:
<dependencies>
        <!--SpringDataJPA-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--Web支持-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--QueryDSL支持-->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <scope>provided</scope>
        </dependency>
        <!--QueryDSL支持-->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
            <scope>provided</scope>
        </dependency>
     </dependencies>   
application.properties配置
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
JPAQueryFactory配置
@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;

在这里插入图片描述

插入一个jpa 注解 @Temporal
在这里插入图片描述

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。

介绍QueryDSL

在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;
    }
多表使用
一、@OneToOne关系映射

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;
}
二、@OneToMany 和 @ManyToOne

实体 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)

三、多对多 @ManyToMany

实体 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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值