springboot整合Spring Data JPA

springboot整合Spring Data JPA

一、概念

JPA是The Java Persistence API标准,Java持久层API,是一种能让对象能够快速映射到关系型数据库的技术规范。
Spring Data JPA 是采用基于JPA规范的Hibernate框架基础下提供了Repository层的实现。Spring Data Repository极大地简化了实现各种持久层的数据库访问而写的样板代码量,同时CrudRepository提供了丰富的CRUD功能去管理实体类。SpringBoot框架为Spring Data JPA提供了整合,spring-boot-starter-data-jpa能够让你快速使用这门技术,它提供了以下依赖。

二、简单整合

以下将整理出SpringData简单的增删查改。

1.新建项目

在新建项目中选择springboot项目,选择JPA依赖与MySQL驱动等导入项目中。

2.编写配置文件

application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/jpatest?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    # 默认false,在日志里显示执行的sql语句
    show-sql: true
    hibernate:
      #指定为update,每次启动项目检测表结构有变化的时候会新增字段,表不存在时会新建,如果指定create,则每次启动项目都会清空数据并删除表,再新建
      ddl-auto: update
    database: mysql
    #在建表的时候,将默认的存储引擎切换为 InnoDB
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

spring.jpa.hibernate.ddl-auto第一建表的时候可以create,指明在程序启动的时候要删除并且创建实体类对应的表。后续使用就需要改为update。

3.编写实体类

需要写好类与表的映射关系。

@Entity // @Entity: 实体类, 必须
@Table(name = "t_user")
@AllArgsConstructor//lombok相关注解
@NoArgsConstructor
@Data
public class User {

    /**
     * TABLE: 使用一个特定的数据库表格来保存主键
     * SEQUENCE: 根据底层数据库的序列来生成主键,条件是数据库支持序列。这个值要与generator一起使用,generator 指定生成主键使用的生成器(可能是orcale中自己编写的序列)。
     * IDENTITY: 主键由数据库自动生成(主要是支持自动增长的数据库,如mysql)
     * AUTO: 主键由程序控制,也是GenerationType的默认值。
     */
    @Id	//主键id
    @GeneratedValue(strategy=GenerationType.IDENTITY)//主键生成策略
    @Column(name="id")//数据库字段名
    private Integer id;

    @Column(name="name",nullable = false) // @Column: 对应数据库列名,可选, nullable 是否可以为空, 默认true
    private String name;

    @Column(name="age")
    private Integer age;

    @Column(name="address")
    private String address;

}

4.运行项目

运行项目后,可以看到控制台结果:
控制台显示使用hibernate创建了该表。
在这里插入图片描述
数据库中的结果

由此,表创建好了。

5.编写dao层

需要继承JpaRepository,关于可以继承的四个类如下:

代码如下:

/**
 * 参数一 T :当前需要映射的实体
 * 参数二 ID :当前映射的实体中的OID的类型
 */

public interface UsersRepository extends JpaRepository<User,Integer> {
    //可以根据自己的需要以及驼峰命名来写自己所需要的的方法!
    //方法名称必须要遵循驼峰式命名规则,findBy(关键字)+属性名称(首字母大写)+查询条件(首字母大写)
    List<User> findByName(String name);
}

只要实现了JpaRepository这个类就可以直接用来测试了,不需要再加原来的@Repository注解。

6.测试

6.1 增加操作 + 查询操作
@SpringBootTest
class SpringbootJpaApplicationTests {
    @Autowired
    private UsersRepository repository;
    @Test
    void contextLoads() {
        User user = new User();
        user.setName("ChaEunWoo");
        user.setAge(23);
        user.setAddress("Korea?");
        repository.save(user);
        List<User> userList = repository.findByName("ChaEunWoo");
        userList.forEach(System.out::println);
    }
}

输出结果:
在这里插入图片描述
如图,数据插入成功了。在这里插入图片描述
当使用findById查询时,返回的是一个optional实例对象。

Optional<User> users = repository.findById(1);
System.out.println(users);
//或者使用下面的
User user = usersDao.getById(3);
System.out.println(user);

结果是使用find查询的结果在这里插入图片描述

6.2 修改操作

代码如下:(如果测试爆红no session不通过,一定要加入@Transactional注解)
但是,如果加入了事务的注解,在测试类中使用事务注解会导致回滚!!因此还需要一个注解使事务不回滚: @Rollback(value = false) 。

    @Transactional
    @Rollback(value = false)
    @Test
    void contextLoads() {
        //如果传入的对象有ID,而且hibernate在数据库能够查到,则save用作修改,否则为新加数据
        System.out.println(usersDao.getById(1));
        User user = new User();
        user.setId(1);
        user.setName("ChaEunWoo");
        user.setAge(24);
        user.setAddress("Korea");
        usersDao.save(user);
        System.out.println(usersDao.getById(1));
    }
6.3 删除操作
    @Test
    public void toDelete(){
        List<User> all = usersDao.findAll();
        all.forEach(System.out::println);
        usersDao.deleteById(4);
        all = usersDao.findAll();
        all.forEach(System.out::println);
    }

在这里插入图片描述

6.4 查询方法

findOne:立即查询
getOne:延迟查询,内部先返回一个客户的动态代理对象,当使用到它的时候才会去数据库查询

三、SpringData运行原理

  1. 通过JdkDynamicAopProxy的invoke方法创建了一个动态代理对象。
  2. SimpleJpaRepository当中封装了JPA的操作(借助JPA的api完成CRUD)
  3. 通过hibernate完成了对数据库的操作(封装了JDBC)

四、其他及多条件查询

1. count及exist

@Test
public void test01(){
	long count = usersDao.count();//得出有多少条数据
	System.out.println(count);

	boolean result = usersDao.existsById(1);//查询id为1的数据是否存在
	System.out.println(result);
}

由图可以看出,ID查找用户是否存在使用的是count来判断!
在这里插入图片描述

2. jpql查询

2.1 简介

jpql:jpa query language(jpa查询语句)
查询的语句(表替换成类)必须是类和类中的属性!

2.2 使用

方法:

  • 将jpql配置在接口方法上面
  • 使用注解 @query
    /**
     * 使用jpql语言进行查询
     * jpql : from User where name = ?
     * @param name
     * @return
     */
    @Query(value = "from User where name = ?1")
    List<User> findJpql(String name);

	//测试方法
    @Test
    public void test02(){
        List<User> users = usersDao.findJpql("HuangXin");
        users.forEach(System.out::println);
    }

结果:
在这里插入图片描述
查询可以自己根据SpringData jpa官方名称查找命名直接查询或者直接自己写query语句查新。

//方法名称必须要遵循驼峰式命名规则,findBy(关键字)+属性名称(首字母大写)+查询条件(首字母大写)
User findByNameAndId(String name,Integer id);

@Query(value = "from User where name = ?1 and id = ?2")
User findByJpql(String userName, Integer UserId);

更新操作时,需要再加上一条 @Modifying 注解!

    @Query(value = "update User set name = ?1 where id = ?2")
    @Modifying
    int updateJpql(String name,Integer id);
   
#测试
	@Test
    @Transactional
    @Rollback(value = false)
    public void test05(){
        int result = usersDao.updateJpql("WWWWWW", 5);
        System.out.println(result);
    }

结果:
在这里插入图片描述
@Query注解中还有一个nativeQuery字段
该字段为true时,表示value中的语句使用SQL语句查询
该字段为false时,表示value中的语句使用内部查询(即使用类名以及属性)

    @Query(value = "select * from t_user ",nativeQuery = true)
    List<User> findAllJpql();

在这里插入图片描述

3. SpringData JPA官方规则定义查询
//方法名称必须要遵循驼峰式命名规则,findBy(关键字)+属性名称(首字母大写)+查询条件(首字母大写)
//精准匹配
User findByNameAndId(String name,Integer id);

//模糊查询,传递的参数为("%a%")等
List<User> findByNameLike(String name);

//模糊查询+年龄阶段+精准匹配 AgeAfter表示年龄在传入的参数之后的那部分
List<User> findByNameLikeAndAgeAfterAndAddress(String name,Integer age,String address);

在这里插入图片描述

五、Specification动态查询

1. JpaSpecificationExecutor中的方法

public interface JpaSpecificationExecutor<T> {
	//查询对象
    Optional<T> findOne(@Nullable Specification<T> var1);
	//查询列表
    List<T> findAll(@Nullable Specification<T> var1);
	//查询并分页
	//Pageable:分页参数
	//返回值:pageBean,由SpringDataJPA提供
    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
	//查询列表,根据Sort字段排序
    List<T> findAll(@Nullable Specification<T> var1, Sort var2);
	//统计查询
    long count(@Nullable Specification<T> var1);
}

2. 查询条件

Specification:查询条件,我们需要定义自己的Specification从而使用JpaSpecificationExecutor的方法。
我们需要实现的方法如下:

//参数解析
//Root:查询的根对象(查询的任何属性都能从根对象中获取)
//CriteriaQuery:顶层查询对象,自定义查询方式(了解,一般用不上)
//CriteriaBuilder:查询的构造器,包含了许多的查询条件
//
Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);

3. 查询测试

3.1 测试一 精确查询

精确查询

    @Test
    public void test08(){
        Specification<User> specification = new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //root 获取要查询的对象属性
                //使用CriteriaBuilder构造查询条件,内含模糊匹配、精准匹配,不等于等条件

                //步骤
                //1.获取比较的属性(是User对象中的属性!)
                Path<Object> name = root.get("name");
                //2.构造查询条件,equal为精准查询 select * from t_user where name = ?
                Predicate predicate = criteriaBuilder.equal(name, "ChaEunWoo");
                //3.返回查询条件
                return predicate;
            }
        };
        Optional<User> user = usersDao.findOne(specification);
        System.out.println(user);
    }
3.2 测试二 拼接等
==模糊查询+年龄比较+拼接==
    @Test
    public void test09(){
        Specification<User> specification = new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                Predicate p1 = cb.like(root.get("name"), "%a%");
                Predicate p2 = cb.ge(root.get("age"),15);
                Predicate predicate = cb.and(p1,p2);//and or 即相当于拼接
                return predicate;
            }
        };
        List<User> userList = usersDao.findAll(specification);
        userList.forEach(System.out::println);
    }
3.3 测试三 排序

添加排序
在测试二基础上,新建排序对象,加到findAll方法里即可。

 //排序
    @Test
    public void test10(){
        Specification<User> specification = new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                Predicate p1 = cb.like(root.get("name"), "%a%");
                Predicate p2 = cb.ge(root.get("age"),15);
                Predicate predicate = cb.and(p1,p2);//and or 即相当于拼接
                return predicate;
            }
        };
        //添加排序
        //创建排序对象,新版本只能通过Sort.by来创建对象(springboot2.1版本以上 Sort已经不能用 new 实例化了)
        //参数1:正序Sort.Direction.ASC/倒序Sort.Direction.DESC
        //参数二:排序的 属性! 名称,对象的属性名称!
        List<String> list = new ArrayList<>();
        list.add("age");
        Sort sort = Sort.by(Sort.Direction.ASC,"age");

        List<User> userList = usersDao.findAll(specification,sort);
        userList.forEach(System.out::println);
    }

测试结果:在这里插入图片描述

3.4 测试四 分页查询

分页查询
在这里插入图片描述

/**
     * 分页查询
     *  参数一:Specification--查询条件
     *  参数二:PageAble--分页参数
     *      分页参数:查询的页码,每页查询的条数
     *      findAll(Specification,Pageable):带有条件的分页查询
     *      findAll(Pageable):不带条件的分页查询
     *  返回值:Page(SpringDataJpa为我们封装好的pageBean对象,数据列表,总条数)
     */
    @Test
    public void test11(){
        /**
         * 使用PageRequest.of来生成Pageable实现类
         *      参数1:当前查询页
         *      参数2:页大小(即一页多少条数据)
         *      参数3:定义排序        --参数可省
         *      参数4:依据什么属性排序    --可省可省
         */
        Pageable pageable = PageRequest.of(0,5);
        Specification<User> specification = null;
        Page<User> page = usersDao.findAll(specification, pageable);
//        page.getTotalElements();//得到所有数据总数
//        page.getTotalPages();//得到总页数
//        page.getContent();//得到需要查询的页面的数据集合
        System.out.println("The data total number is "+page.getTotalElements());
        System.out.println("The total page number is "+page.getTotalPages());
        page.getContent().forEach(System.out::println);
    }

测试结果:在这里插入图片描述

六、多表关系与多表操作

1. 多表关系

(1)一对一
(2)一对多
一的一方:主表
多的一方:从表
外键:需要在从表上新建一列作为外键,它来源于主表的主键
(3)多对多
中间表:中间表最少应该有两个字段组成,这两个字字段作为外键指向两张表的主键,组成了联合主键

2. 多表操作

2.1 一对多关系操作

假设一家公司有多个员工,一个员工从属于一家公司。
实体类编写如下:
Employee.java

//一对多的从体
@Entity
@Table(name = "t_employee")
public class Employee {
    @Id()
    @GeneratedValue(strategy= GenerationType.IDENTITY)//主键生成策略
    @Column(name="emp_id")
    private int empId;

    @Column(name="emp_name")
    private String empName;

    @Column(name="emp_age")
    private int empAge;

    //配置多对一关系,与一对多差不多
    @ManyToOne(targetEntity = Company.class)
    @JoinColumn(name = "emp_com_id",referencedColumnName = "com_id")
    private Company company;


    public int getEmpId() {
        return empId;
    }

    public void setEmpId(int empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    public int getEmpAge() {
        return empAge;
    }

    public void setEmpAge(int empAge) {
        this.empAge = empAge;
    }

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }

    public Employee(int empId, String empName, int empAge, Company company) {
        this.empId = empId;
        this.empName = empName;
        this.empAge = empAge;
        this.company = company;
    }

    public Employee() {
    }

    @Override
    public String toString() {
        return "Employee{" +
                "empId=" + empId +
                ", empName='" + empName + '\'' +
                ", empAge=" + empAge +
                ", company=" + company +
                '}';
    }
}

Company.java

//一对多的主体
@Entity
@Table(name = "t_company")
public class Company {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)//主键生成策略
    @Column(name="com_id")
    private int comId;

    @Column(name="com_name")
    private String comName;

    @Column(name="com_mail")
    private String comMail;

    @Column(name="com_address")
    private String comAddress;


    //声明关系,配置一对多的关系
    //配置外键(中间键)  name:外键字段名称  referencedColumnName:参照主表的主键名称
    @OneToMany(targetEntity = Employee.class,cascade = CascadeType.ALL)
    //targetEntity指对方的字节码文件,cascade级联操作,要改一起改,要删一起删,在主表上配置!!
    @JoinColumn(name = "emp_com_id",referencedColumnName = "com_id")
    private Set<Employee> employeesSet = new HashSet<>();

    public int getComId() {
        return comId;
    }

    public void setComId(int comId) {
        this.comId = comId;
    }

    public String getComName() {
        return comName;
    }

    public void setComName(String comName) {
        this.comName = comName;
    }

    public String getComMail() {
        return comMail;
    }

    public void setComMail(String comMail) {
        this.comMail = comMail;
    }

    public String getComAddress() {
        return comAddress;
    }

    public void setComAddress(String comAddress) {
        this.comAddress = comAddress;
    }

    public Set<Employee> getEmployeesSet() {
        return employeesSet;
    }

    public void setEmployeesSet(Set<Employee> employeesSet) {
        this.employeesSet = employeesSet;
    }

    public Company() {
    }

    public Company(int comId, String comName, String comMail, String comAddress, Set<Employee> employeesSet) {
        this.comId = comId;
        this.comName = comName;
        this.comMail = comMail;
        this.comAddress = comAddress;
        this.employeesSet = employeesSet;
    }

    @Override
    public String toString() {
        return "Company{" +
                "comId=" + comId +
                ", comName='" + comName + '\'' +
                ", comMail='" + comMail + '\'' +
                ", comAddress='" + comAddress + '\'' ;
    }
}

执行前:
在这里插入图片描述
在外键可以为null的情况下,删除某公司后,数据表中公司id为空:
在hibernate一对多的注解中,由于没有添加mappedBy=“多端的关联属性名”,导致在更新数据时,外键被置空(null),意思是由多的一端来维护关系。(一般由有外键的一方来维护)
在这里插入图片描述

2.2 多对多关系操作

暂略…

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值