springboot体系中一个持久层框架,只需要定义好实体类和接口,便可以调用相应的方法对数据库进行基本的增删查改的工作,比起mybatis,不需要写配置文件,sql语句即可完成对数据库的操作;
对于jpa的基本操作:
首先引入依赖,建立springboot工程:
<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>
数据库中的建表语句:
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;
以一个商品类别表演示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容器中,就可以直接调用基本的增删改查的方法了:
@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); } }
对于查询,单个的查询有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方法内即可;
条件查询:
可以采用这一组方法:
例子如下:
@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;