[java学习笔记]spring-data jpa 的简单使用

时间:2023-02-04 16:10:56

springboot体系中一个持久层框架,只需要定义好实体类和接口,便可以调用相应的方法对数据库进行基本的增删查改的工作,比起mybatis,不需要写配置文件,sql语句即可完成对数据库的操作;

对于jpa的基本操作

首先引入依赖,建立springboot工程:

[java学习笔记]spring-data jpa 的简单使用[java学习笔记]spring-data jpa 的简单使用
<dependencies>
        <!--spring 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>
        </dependency>
        <!--lombook-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--测试依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>
View Code

数据库中的建表语句:

[java学习笔记]spring-data jpa 的简单使用[java学习笔记]spring-data jpa 的简单使用
CREATE TABLE `product_category` (
  `category_id` int(11) NOT NULL AUTO_INCREMENT,
  `category_name` varchar(64) NOT NULL COMMENT '类目名字',
  `category_type` int(11) NOT NULL COMMENT '类目编号',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`category_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
View Code

以一个商品类别表演示jpa的操作;

建立对应的实体类:

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Date;

@Entity
// @Table(name = "product_category") 驼峰模式自动映射,如果不是可以使用这个注解,javax.persistence包下的
@DynamicUpdate // 动态更新,对于由数据库维护的例如更新时间的自动更新,如果属性中有更新时间对应的字段,那么更新
// 时就需要手动更新而使数据库的自动更新生效,增加上这个注解,可以使得该效果生效,亦即在有更新时间字段时也能自动更新该字段
@Data
@ToString
@NoArgsConstructor
public class ProductCategory {
    // 主键
    @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增类型的主键
//    @Column(name = "category_id")  列和属性的映射,驼峰模式自动映射,如果不是需要额外设置
    private Integer categoryId;
    // 类别名称
    private String categoryName;
    // 类别编号
    private Integer categoryType;


    private Date updateTime;
    private Date createTime;
}

  需要注意的是:

  类名和表明的映射关系,默认是驼峰模式,即对于数据库表名:多个单词之间使用'_'连接,对于类名而言:多个单词直接并列写,每个单词首字母都大写,这样的命名是可以自动映射的,否则需要增加table注解来指定对应的表名,@DynamicUpdate动态更新注解,一般来说不需要,对于上述例子,更新时间这个属性在数据库中自动维护,如果在调用save函数时,updateTime这个属性传值了,那么在没有添加@DynamicUpdate注解的情况下,最终更新在数据库中的update_time这个列的值便是手动传入的值,如果加上的话,结果便是数据库自动维护当前时间的值;

  java属性和数据库列名的映射:针对默认的驼峰模式的命名方法可以进行自动的映射,当然,也可以通过@Column注解来手动提供映射关系;

  主键的注解@Id,声明该属性对应的字段为主键,可以添加注解@GeneratedValue,这个注解指定主键自动生成的策略,在这个模式下使用的是自增;

定义持久层接口:

  

import cn.ly.domain.ProductCategory;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductCategoryRepository extends JpaRepository<ProductCategory,Integer> {
}

关于这个接口的说明:需要继承JpaRepository类,两个泛型:第一个为实体类,第二个为主键类型;

将接口注入到spring容器中,就可以直接调用基本的增删改查的方法了:

[java学习笔记]spring-data jpa 的简单使用[java学习笔记]spring-data jpa 的简单使用
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProductCategoryRepositoryTest {
    @Autowired
    ProductCategoryRepository repository;

    @Test
    public void testquery() {
        // 查找全部
        List<ProductCategory> all = repository.findAll();
        System.out.println(all);
    }

    @Test
    public void testqueryone() {
        // 查找单个
        Optional<ProductCategory> optional = repository.findById(1);
        if (optional.isPresent()) {
            ProductCategory productCategory = optional.get();
            System.out.println(productCategory);
        }
    }

    @Test
    public void add() {
        ProductCategory productCategory = new ProductCategory();
        productCategory.setCategoryName("新增类目");
        productCategory.setCategoryType(10);
        // 新增,主键有jpa自动生成,创建时间和更新时间由数据库管理
        // 返回的对象包含了自动生成的主键的信息;
        ProductCategory save = repository.save(productCategory);
        System.out.println(save);
    }

    @Test
    public void edit() {
        // 更新,和新增调用相同的函数,在添加了主键的情况下,会修改对应的值
        // 修改主键为6的信息
        // 先查询
        Optional<ProductCategory> optional = repository.findById(6);
        if (optional.isPresent()) {
            ProductCategory productCategory = optional.get();
            // 修改
            productCategory.setCategoryName("新增类目修改");
            ProductCategory save = repository.save(productCategory);
        }
    }

    @Test
    public void delete() {
        // 删除
        repository.deleteById(6);
    }

}
View Code

对于查询,单个的查询有findone和findById两个方法,后者返回一个optional的对象,这个对象的存在主要是提醒程序员进行空的检查,避免空指针异常;

对于修改:先根据id查询的意义在于首先获得一个可用的,包含了该条记录的所有信息的对象,然后对希望修改的属性进行单独的设值,最后储存进去;在属性较多,只希望修改某些部分的属性时,推荐采用这样的流程修改数据,以免改了不希望修改的属性;

对于删除:这里调用的是根据id删除的方法,还有一个delete方法,参数为一个希望删除的对象;

对于分页和排序:

对于分页和排序,所要做是调用包含了参数Pagealbe的搜索方法:

 
 
@Test
public void queryPage() {
// 排序和分页,需要传递Pageable和Sort
Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "updateTime"));
Page<ProductCategory> all = repository.findAll(pageable);
// 总的数量
long totalElements = all.getTotalElements();
// 总的页数
int totalPages = all.getTotalPages();
// 内容
List<ProductCategory> content = all.getContent();
System.out.println(all);
}
 

对于findAll的重载参数有Pageable和Sort两种类型,前者可以进行分页,后者则是排序,如果需要同时分页和排序的话则要采用Pageable的重载类型

首先说分页:pageable的构建函数中,第一个参数为页码,第二个参数为每页的数据多少,这个页码是从0开始计数的;如果需要排序,则传递第三个参数Sort,这个对象的构建函数的参数:第一个指定了是顺序还是倒叙,第二个参数希望按照的排序属性,是个可变参数, 可以传递多个值;返回的page类型

如果只需要排序,不分页的话,将sort对象作为参数直接传递到findAll方法内即可;

条件查询:

可以采用这一组方法:[java学习笔记]spring-data jpa 的简单使用

例子如下:

 @Test
    public void queryExam() {
        // 确定条件
        ProductCategory productCategory = new ProductCategory();
        productCategory.setCategoryName("销");
        // 创建匹配的模式
        ExampleMatcher matcher = ExampleMatcher.matching();
        matcher = matcher.withMatcher("categoryName", ExampleMatcher.GenericPropertyMatchers.contains());
        // 创建样本
        Example<ProductCategory> example = Example.of(productCategory, matcher);
        List<ProductCategory> all = repository.findAll(example);
        System.out.println(all);
    }

对于这段代码的说明:首先创建一个实体类,实体类包含的信息即为要查询的条件,然后要根据属性设置匹配模式,这里设置的是categoryName只要包含条件中的子符串即可被查到,多个属性配置的话可以重复调用withMatcher方法即可;

自定义方法

  可以通过自定义一些符合命名规范的方法来进行特殊的查询:

List<ProductCategory> findByUpdateTimeBefore(Date updateTime);

    Page<ProductCategory> findByUpdateTimeBefore(Date updateTime, Pageable pageable);

方法定义在之前创建的ProductCategoryRepository接口中,命名为findByxxxxAndxxxx ,xxxx为属性名,后面可以跟着before,in等字段做条件查询,如果需要分页,则返回为Page类型的值,同时参数额外传递Pageable; 这些方法可以直接使用,不需要自己写实现;

还可以自己写sql:

public interface ProductInfoRepository extends JpaRepository<ProductInfo,String> {
    List<ProductInfo> findByProductStatus(Integer status);

    @Modifying
    @Query("update ProductInfo p set p.productStock = p.productStock-:desStore where p.productId=:productId and p.productStock>=:desStore")
    int decreaseStore(@Param("productId") String productId, @Param("desStore") Integer desStore);

    @Modifying
    @Query("update ProductInfo p set p.productStock = p.productStock+:num where p.productId=:productId")
    int increaseStore(@Param("productId") String productId, @Param("num") Integer num);

}

注意一点:这里的sql语句写的都是java类的名字以及属性名,参数使用“:参数名”的形式传递;

这样的方法也是可以直接调用的;

总结:

  jpa是非常方便的操作数据库的框架,针对单表的开发比mybatis要方便;对于如何选择的问题,个人看法:单表操作用jpa,多表复杂的查询操作使用mybatis;