Spring Boot之JPA了解与使用

JPA相关介绍

JPA介绍

JPA(Java Persistence API)是 Sun 官方提出的 Java 持久化规范。它为 Java 开发人员提供 了一种对象 / 关联映射工具来管理 Java 应用中的关系数据。它的出现主要是为了简化现有的 持久化开发工作和整合 ORM 技术,结束现在 Hibernate、TopLink、JDO 等 ORM 框架各自为 营的局面。

注意: JPA 是一套规范,不是一套产品,那么像 Hibernate、TopLink、JDO 它们是一套产 品,如果说这些产品实现了这个 JPA 规范,那么我们就可以称他们为 JPA 的实现产品。

Spring Data JPA介绍
  1. Spring Data 是 Spring 的一个子项目,用于简化数据库访问,包括 NoSQL 非关系型数据库, 另外还包括对关系型数据库的访问支持。Spring Data 使我们可以快速简单地使用普通的数据访 问技术及新的数据访问技术,Spring Data 会让数据的访问变得更加方便。

  2. Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架, 可以让开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增、删、改、查等在 内的常用功能,且易于扩展,学习并使用 Spring Data JPA 可以极大提高开发效率。Spring Data JPA 其实就是 Spring 基于 Hibernate 之上构建的 JPA 使用解决方案,方便在 Spring Boot 项目中 使用 JPA 技术。

  3. Spring Data JPA 让我们解脱了 DAO 层的操作,基本上所有 CRUD 都可以依赖于它实现。

快速上手

  1. 创建项目

    选择创建Spring Initializr项目,然后选择如下几个:
    在这里插入图片描述

  2. 导入依赖(选择了图中的几个之后就会自动生成在pom中,只需更改版本即可)

    <!-- 导入该依赖可以 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- mysql依赖,版本号为自己的mysql数据库版本 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.18</version>
    </dependency>
    
    <!-- 导入该依赖后可使用lombok插件提供的注解和方法等 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    
  3. 添加配置文件

    # 数据源
    spring.datasource.url=jdbc:mysql://localhost:3306/boot?useUnicode=true&characterEncoding=utf-8
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    
    # mysql方言
    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
    # 是否在控制台输出sql语句
    spring.jpa.show-sql=true
    # 格式化sql语句,让sql语句更加清晰易懂
    spring.jpa.properties.hibernate.format_sql=true
    
  4. 创建实体类、DAO层和业务逻辑层等

    实体类

    注解解析:

    1. @Entity ( name = “数据库表名” )

      必须要写! 用来标注一个数据库对应的实体,数据库中创 建的表名默认和类名一致。 其中name可以省略,因为使用这种方法的话可能导致出错!!!

    2. @Table ( name=“数据库表名”,catalog="",schema="" )

      ​ 可以不写!一般配合@Entity注解使用, 用来标注一个数据库对应的实体,数据库中创 建的表名默认和类名一致。只能标注在实体类上!

    3. @Data

      ​ 可以不写!为实体类提供了get和set等方法,不包括有参方法和无参方法!

    4. @Id

      ​ 必须要写! 定义了映射到数据库表的主键的属性 ,一个实体类只能有一个属性被映射为主键!!!

    5. @ GeneratedValue ( strategy=GenerationType,generator="" )

      ​ 可以不写!strategy: 表示主键 生成策略,有 AUTO、INDENTITY、SEQUENCE 和 TABLE 4 种,分别表示让ORM 框 架自动选择,这一章先只用到IDENTITY。generator: 表示主键生成器的名称。

    6. @Column ( name = “数据库字段名”, nullable = false,unique=false ,length=32 )

      ​ 可以不写! 描述了 数据库表中该字段的详细定义,这对于根据 JPA 注解生成数据库表结构的工具。 name:表示 表示数据库表中该字段的名称,默认情形属性名称一致 ;

      ​ nullable : 表示该字段是否允许为 null,默认为 true;

      ​ unique: 表示该字段是否是唯一标识,默认为 false;

      ​ length: 表示该字段的大小,仅对 String 类型的字段有效;

    DAO层

JPA中dao层可以继承 JpaRepository<T,ID> 类 和 JpaSpecificationExecutor 类,这两个类都提供了很多的常用方法,比如常用的增删改查还有分页等。

// Dao层
public interface UserRepository extends JpaRepository<实体类类型,主键类型>, JpaSpecificat
ionExecutor<实体类类型> {
	// 还可自定义方法进行使用
}

如何打开类图: 在当前类的大括号内的区域右键单击,选择 Diagrams | Show Diagram 选项,即可打开类图!打开后效果如图所示:
在这里插入图片描述

通过上图我们发现 JpaRepository 继 承 PagingAndSortingRepository 和 QueryByExampleExecutor, PagingAndSortingRepository 类主要负责排序和分页内容;

QueryByExampleExecutor 提供了很多示例的查询方法;

因此,继承 JpaRepository 的 Repository 会自动拥有示例查询方法和排序、分页功能。

继续往上查看 我们发现 PagingAndSortingRepository 又继承了 CrudRepository 。

CrudRepository 内置了我们最常用的增、删、改、查的方法;

Service(业务逻辑层)

​ 该层暂时只需创建方法然后调用dao层中的方法返回数据即可。

测试层(以下方法都为自带方法,自动生成SQL)

@SpringBootTest
@RunWith(SpringRunner.class)
public class StudentTests {
    @Resource
    private StudentDao studentDao;

    //新增
    @Test
    public void testInsertStudent(){
        Student student = new Student("xixi","1234","女",2);
        //save:该方法有id值时为修改,没有id值时为新增,返回对象类型
        Student row = studentDao.save(student);	
        if (row != null){
            System.err.println("==============>新增成功!");
        }else{
            System.err.println("==============>新增失败!");
        }
    }

    //删除
    @Test
    public void testDeleteStudent(){
        try{
            studentDao.deleteById(6);	//该方法返回void类型
            System.err.println("=============》删除成功!");
        }catch (Exception e){
            System.err.println("=============》删除失败!");
            e.printStackTrace();
        }
    }

    //修改
    @Test
    public void testUpdateStudent(){
        Student student = new Student();
        student.setStuId(4);
        student.setStuName("hhh");
        student.setStuPassword("1234");
        student.setStuSex("男");
        student.setStuGrade(1);
        //save:该方法有id值时为修改,没有id值时为新增,返回对象类型
        Student row = studentDao.save(student);
        if(row != null){
            System.err.println("=================>修改成功!");
        }else {
            System.err.println("=================>修改失败!");
        }
    }

    //查询所有
    @Test
    public void testSelectStudent(){
        List<Student> list = studentDao.findAll();	//该方法返回List集合类型
        for (Student student:list){
            System.err.println(student.toString());
        }
    }
}
自定义查询(简单操作)

Spring Data JPA 还可以根据接口方法名来实现数据库操作,主要的语法是 findXXBy、 readXXBy、queryXXBy、countXXBy、getXXBy 后面跟属性名称,利用这个功能仅需要在定义的 Repository 中添加对应的方法名即可,使用时 Spring Boot 会自动帮我们自动编写sql然后实现。

注: 这种方式多用于查询,不用于增删改!!!

示例:

// 根据id查询用户
public User findByUsrId(Integer usrId);

// 用户登录
public User findByUsrNameAndUsrPassword(String usrName,String usrPassword);

以上示例方法命名规范调用使用时会自动生成动态SQL语句进行实现!

自定义查询(复杂操作)

使用 Spring Data 大部分的查询都可以根据方法名定义的方式来实现,但是由于某些原因 必须使用自定义的 QL 来查询,Spring Data 也可以完美支持。在dao层方法上加入@Query注解来编写HQL或SQL进行自定义操作。

HQL示例(推荐使用):

// 根据id查询用户
@Query(value = "select u from User u where u.usrId=:usrId")
public User SelectUserByUsrId(@Param("usrId") Integer usrId);

//查询单独几列时需要在HQL语句中 new map 或 new User。比如:
// 1. 按用户名进行查询(使用new map可返回 List 类型或 Map 类型)
@Query(value = "select new map(u.usrId,u.usrName) from User u where u.usrName=:usrName")
public Map SelectByName(@Param("usrName") String usrName);

// 2. 按照id进行查询(使用new User需要在User实体类中创建对应的有参方法)
@Query(value = "select new User(u.usrId,u.usrName) from User u where u.usrId=:id")
public User SelectById(@Param("id") Long id);

/*
 如涉及到删除和修改需要加上 @Modifying,也可以根据需要添加 @Transactional 对事务的支持、操作超时设置等。
*/
@Transactional(timeout = 10)
@Modifying
@Query("update User s set s.usrName=:name where s.usrId=id")
public int UpdateByUsrId(@Param("name") String Name,@Param("id") Long Id);

SQL示例(不推荐使用):

// 根据id删除用户
@Query(value = "delete from user where usr_id=:usrId",nativeQuery = true)
public User DeleteUserByUsrId(@Param("usrId") Integer usrId);

// 根据id查询用户
@Query(value = "select * from User where usr_id=:usrId",nativeQuery = true)
public User SelectUserByUsrId(@Param("usrId") Integer usrId);

/**
 * 因为sql语句和以前的都是一样的,这里就不一一举例了
 */

注解解析:

@Query(value="",nativeQuery=true)注解

  1. 赋值可以使用?1、?2…的写法(不推荐)或者使用别名(@Param(" ")的方式(推荐),nativeQuery等 于true表示这是原生的SQL。

  2. 编写SQL语句:可以使用 * 号。
    select * from sys_user where usr_id=:id
    select * from sys_user where usr_id=?1

  3. 编写HQL语句:不能使用 * 号,且必须要取别名(思想)!
    select u from User u where u.usrName=:name
    select u from User u where u.usrName=?1

  4. SQL是根据数据库表的表名和字段名进行操作,而HQL是实体类的类名和属性名来进行操作。

  5. 编写SQL语句时需要加上 nativeQuery = true !

分页查询

Spring Data JPA 已经帮我们内置了分页功能,在查询的方法中,需要传入参数 Pageable, 当查询中有多个参数的时候 Pageable 建议作为最后一个参数传入。

Pageable 是 Spring 封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则, Page 是 Spring 封装的分页对象,封装了总页数、分页数据等。返回对象除使用 Page 外,还可以使用 Slice 作为返回值。

Page 和 Slice 的区别:

  1. Page 接口继承自 Slice 接口,而 Slice 继承自 Iterable 接口。
  2. Page 接口扩展了 Slice 接口,添加了获取总页数和元素总数量的方法,因此,返 回 Page 接口时,必须执行两条 SQL,一条复杂查询分页数据,另一条负责统计数 据数量。
  3. 返回 Slice 结果时,查询的 SQL 只会有查询分页数据这一条,不统计数据数量。
  4. 用途不一样:Slice 不需要知道总页数、总数据量,只需要知道是否有下一页、上 一页,是否是首页、尾页等,比如前端滑动加载一页可用;而 Page 知道总页数、 总数据量,可以用于展示具体的页数信息,比如后台分页查询。

示例:

/**
 * Dao层
 * 分页查询:
 * Spring Data JPA 已经帮我们内置了分页功能,在查询的方法中,需要传入参数 Pageable,
 * 当查询中有多个参数的时候 Pageable 建议作为最后一个参数传入。
 */
public interface UserDao extends JpaRepository<User,Long> {
    //分页查询
    @Query("select u from User u where u.usrRoleId=:roleId")
    public Page<User> findByUsrRoleId(@Param("roleId") Long roleId, Pageable pageable);
}
	@Resource
    private UserDao userDao;

	@Test
    public void testPage(){
        int page = 0;	//第几页,一般从第0页开始
        int size = 2;	//每页显示多少条数据

        //数据排序(默认为ASC(升序)从小到大,DESC为(降序)从大到小),这里按照id进行降序操作
        Sort sort = Sort.by(Sort.Direction.DESC, "usrId");

        // 控制分页的辅助类,可以设置页码、每页的数据条数、排序等
        Pageable pageable = PageRequest.of(page, size, sort);
        Page<User> userPage = userDao.findByUsrRoleId(2L, pageable);
        System.err.println("总记录数 --------> " + userPage.getTotalElements());
        System.err.println("总页数 ----------> " + userPage.getTotalPages());
        System.err.println("当前页数 --------> " + userPage.getNumber());
        System.err.println("每页记录数 ------> " + userPage.getNumberOfElements());

        //获得查询记录
        System.err.println("当前记录: ------> ");
        for (User user : userPage.getContent()) {
            System.out.println("usr_name=" + user.getUsrName());
        }
    }

复杂操作:

  1. 我们可以通过 AND 或者 OR 等连接词来不断拼接属性来构建多条件查询,但如果参数大 于 6 个时,方法名就会变得非常的长,并且还不能解决动态多条件查询的场景。这种时候就需要给大家介绍另外一个利器JpaSpecificationExecutor 了。

  2. JpaSpecificationExecutor 是 JPA 2.0 提供的 Criteria API 的使用封装,可以用于动态生成 Query 来满足我们业务中的各种复杂场景。 Spring Data JPA 为我们提供了 JpaSpecificationExecutor 接口,只要简单实现 toPredicate 方法就可以实现复杂的查询。

  3. 在前面的那些方法都只需要使用JpaRepository<T, ID>即可使用对应方法,而当出现参数过大的情况下就可以使用JpaSpecificationExecutor 接口。

示例:

/**
 * Dao层
 */
public interface UserDaoMapper extends JpaRepository<User,Long>,JpaSpecificationExecutor<User> {
    //使用复杂查询实现分页,dao层无需写任何方法
}

省略业务逻辑层代码…

/**
 * 测试类
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserTests {
    @Resource
    private UserDaoMapper userDaoMapper;

    @Test  //复杂操作;根据用户名进行模糊查询(使用提供的方法,自定义方法尚不知晓)
    public void testSelectByUsrNameLike(){
        //创建map对象,用于提供条件
        Map map = new HashMap();
        map.put("Name","lao");

        List<User> list = userDaoMapper.findAll(new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //创建一个集合用于存储条件
                List<Predicate> predicates = new ArrayList<>();

                //判断是否传入有值,生成动态SQL
                if (map.get("Name") != null){
                    predicates.add(criteriaBuilder.like(root.get("usrName"),"%"+map.get("Name")+"%"));
                }
                //条件多的话可以进行使用if进行判断
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        });
		
        //打印输出
        for (User user : list){
            System.err.println(user.toString());
        }
    }
}
多表查询

​ 多表查询在 Spring Data JPA 中有两种实现方式,第一种是创建一个结果集的接口来接收多表联接查询后的结果,第二种是利用 JPA 的关联映射来实现。

1. 创建结果集接口实现

/**
 * 此接口为结构集接口,写在dao层中
 * 可以将需要的字段全部写在这里
 */
public interface UserAndRoleInfo {
    public Long usrId();
    public String usrName();
    public String usrPassword();
    public Long usrRoleId();
    public Integer usrFlag();

    //需要显示的其他表的字段(Role表的name)
    public String roleName();
}
/**
 * UserDao接口方法
 */
//多表联查(必须要给字段名取别名); 根据用户名进行模糊查询
@Query(value = "select new map(u.usrId as usrId,u.usrName as usrName,u.usrPassword as usrPassword,r.roleName as roleName) from User u,Role r where u.usrRoleId=r.roleId and u.usrName like :usrName")
public List<UserAndRoleInfo> InnerJoin(@Param("usrName") String usrName);

省略业务逻辑层代码…

/**
 * 测试类
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserTests {
    //多表联查;根据用户名进行模糊查询,输出id、name、pasword和rolename列
    @Test
    public void testInnerJoin() {
        //返回接口类型则只能返回一条数据,而返回集合类型则可返回多条数据,模糊查询的值必须要加上%
        List<UserAndRoleInfo> list = userService.getInnerJoin("%x%");
        for (UserAndRoleInfo userAndRoleInfo : list) {
            System.err.println("用户信息:" + userAndRoleInfo.toString());
        }
    }
}

2. 关系映射实现

注: 使用关系映射的方式实现多表联查会导致lombok提供的注解失效,所以在实体类层建议大家自行编写get和set方法等!

单向多对一关联(我这里重新创两个表来使用)

/**
 * 实现单向多对一关联
 * 实体类UserSeveralForOne
 */
@Entity
@Table(name = "sys_user")
public class UserSeveralForOne implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "usr_id")
    private Long usrId;

    @Column(name = "usr_name")
    private String usrName;

    @Column(name = "usr_password")
    private String usrPassword;

    /**
     * @ManyToOne(targetEntity=实体类.class):映射一对多的关联关系;targetEntity表示关联实体类型
     * @JoinColumn(name="字段名"):映射外键字段
     */
    @ManyToOne(targetEntity = RoleSeveralForOne.class,fetch = FetchType.EAGER)
    @JoinColumn(name = "usr_role_id")
    private RoleSeveralForOne role;

    @Column(name = "usr_flag")
    private Integer usrFlag;

    /**
   	 * 省略get和ser方法
   	 * 省略有参和无参方法
   	 * 省略toString方法
   	 */
}
/**
 * 实现单向多对一关联
 * 实体类RoleSeveralForOne
 */
@Entity
@Table(name = "sys_role")
public class RoleSeveralForOne implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;

    @Column(name = "role_name")
    private String roleName;

    @Column(name = "role_desc")
    private String roleDesc;

    @Column(name = "role_flag")
    private Integer roleFlag;
    
    /**
   	 * 省略get和ser方法
   	 * 省略有参和无参方法
   	 * 省略toString方法
   	 */
}
/**
 * Dao层方法,接口需要继承JpaRepository<T,ID>
 */
//单向多对一关联,根据roleId查询数据
@Query(value = "select new list(u.usrName,u.usrPassword,r.roleName) from UserSeveralForOne u,RoleSeveralForOne r where u.role=r.roleId and u.role=:roleId")
public List SelectByUsrRoleId(@Param("roleId") RoleSeveralForOne roleId);

省略业务逻辑层代码…

/**
 * 测试类
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserSeveralForOneTests {
    @Resource
    private UserSeveralForOneDao userSeveralForOneDao;

    //单项多对一关联查询
    @Test
    public void testSelectByUsrRoleId(){
        RoleSeveralForOne role = new RoleSeveralForOne();
        role.setRoleId(1L);

        List<UserSeveralForOne> list = userSeveralForOneDao.SelectByUsrRoleId(role);
        /**
         * 这里直接打印,原因:如果使用for循环遍历输出则会报类型异常,因为这是两个实体类,类型不一至
         */
        System.err.println(list.toString());
    }
}
级联映射
/**
 * 实体类RoleSeveralForOne
 * 在该实体类中添加Set<T>
 */
@Entity
@Table(name = "sys_role")
public class RoleSeveralForOne implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;

    @Column(name = "role_name")
    private String roleName;

    @Column(name = "role_desc")
    private String roleDesc;

    @Column(name = "role_flag")
    private Integer roleFlag;
    
    /**
     * targetEntity 属性表示关联的实体类型
     * fetch 属性表示加载策略,EAGER表示立即加载,LAZY表示延迟加载(默认)
     * cascade 属性表示级联操作,PERSIST 表示级联持久化(保存)操作,REMOVE 级联删除,ALL 级联所有操作。
     * 注:两个实体类的加载策略最好一致!
     */
    @OneToMany(targetEntity = UserSeveralForOne.class,fetch = FetchType.EAGER,cascade = CascadeType.ALL,mappedBy = "role")
    private Set<UserSeveralForOne> users = new HashSet<>();
    
    /**
   	 * 省略get和ser方法
   	 * 省略有参和无参方法
   	 * 省略toString方法
   	 */
}

这里为了简便,我们就直接使用Da接口继承的父类提供的方法进行对应操作。

  1. 新增
//级联新增	
@Test   
public void testInsert(){
    RoleSeveralForOne role = new RoleSeveralForOne();
    role.setRoleName("程序员");
    role.setRoleDesc("java");
    role.setRoleFlag(32);

    UserSeveralForOne user = new UserSeveralForOne();
    user.setUsrName("cxy");
    user.setUsrPassword("abab");
    user.setRole(role);
    user.setUsrFlag(12);

    //将要插入的值存进Set<T>接口中,要插入多条时继续使用add()添加即可
    role.getUsers().add(user);

    //调用save()方法,有id值是为修改,无id值时为新增
    RoleSeveralForOne roleSeveralForOne = roleSeveralForOneDao.save(role);
    if (roleSeveralForOne != null){
    	System.err.println("新增成功!");
    }else {
    	System.err.println("新增失败!");
    }
}

新增方法(无id值):S save(S var1);

  1. 删除
//级联删除
@Test
public void testDelete(){
    try {
        roleSeveralForOneDao.deleteById(4L);
        System.err.println("删除成功!");
    }catch (Exception e){
    	System.err.println("删除失败!");
    }
}

删除方法(根据id删除):void deleteById(ID var1);

  1. 修改
//级联修改
@Test   
public void testInsert(){
    RoleSeveralForOne role = new RoleSeveralForOne();
    role.setRoleId(5L);
    role.setRoleName("java程序员");
    role.setRoleDesc("javacxy");
    role.setRoleFlag(24);

    UserSeveralForOne user = new UserSeveralForOne();
    user.setUsrId(23L);	//usrId,根据这个id来进行修改
    user.setUsrName("javaCxy");
    user.setUsrPassword("1234");
    user.setRole(role);
    user.setUsrFlag(25);

    //将要修改的值存进Set<T>中,要修改多条时继续使用add()添加即可
    role.getUsers().add(user);

    //调用save()方法,有id值是为修改,无id值时为新增
    RoleSeveralForOne roleSeveralForOne = roleSeveralForOneDao.save(role);
    if (roleSeveralForOne != null){
    	System.err.println("修改成功!");
    }else {
    	System.err.println("修改失败!");
    }
}

修改方法(有id值):S save(S var1);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值