入门示例
使用 spring boot jpa 来操作数据库的增删改查是非常方便的,定义完 model 之后,直接定义JPA
即可,后续操作就很丝滑了:
@Table(name = "host_spec_price")
@Data
@Entity
public class BudgetHost {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "id", insertable = false, nullable = false)private Long id;@Column(name = "host_spec")private String hostSpec;@Column(name = "host_type")private String hostType;@Column(name = "start_date")private String startDate;@Column(name = "create_time")private Timestamp createTime;@Column(name = "update_time")private Timestamp updateTime;}
定义 JPA 接口:
import org.springframework.data.jpa.repository.JpaRepository;public interface BudgetHostDao extends JpaRepository<BudgetHost, Long> {}
然后在 Controller 里面就可以用了:
@RestController
@Slf4j
public class BudgetController {@Resourceprivate BudgetHostDao budgetHostDao;@GetMapping("/budget/cloudHostBudget")public Result<List<BudgetHost>> getCloudHostBudget(String userName) {List<BudgetHost> list = budgetHostDao.findAll();log.info("userName = {} execute getCloudHostBudget, query size={}", userName, list.size());return Result.success(list);}}
高级场景
入门例子已经能涵盖CURD 80%的需求了,但有时候我们定义的实体类并不会只是简单的 select,可能还要分组聚合,所以你需要使用 JPA 的 Native Query ,并配合一个新对象或接口来接受聚合场景下的对象返回封装。
这里我总结有两种情况:
- 使用接口接受新聚合结果
- 使用类接受聚合结果
使用接口接受聚合结果
假设我们按表里面 day 字段分组,求 count,那么我就 key 定义一个简单的接口接受结果,然后返会给前端
public interface GroupStatistics {String getName();int getValue();
}
JPA 类的定义:
@Query(nativeQuery = true,value = " select day as name, count(*) as value from table group by day
where create_time between :startDate and :endDate and user like CONCAT('%',:user,'%')
")List<GroupStatistics> findApprovalStatistics(@Param("user") String user, @Param("startDate") String startDate,@Param("endDate") String endDate);
ok,看起来还是非常简单的,但是这种用法有一个缺点,就是说如果用接口定义,你拿到返回的数据后,就没办法去再次修改返回结果了,因为这是接口不是类,没办法新增属性和修改属性,基于这种情况下,我们再来看使用类接受定制的查询结果
使用类接受聚合结果
model 定义
import lombok.Data;
import org.hibernate.annotations.Immutable;import javax.persistence.*;@NamedNativeQuery(name = "CloudHostBudgetDTO.budgetQuery",
query = "select SUBSTRING_INDEX(a.tag, ':', -1) as label, " +" count(*) as hostCount, " +" round(sum(b.real_price), 0) as hostPrice, " +" 0 as cbsPrice, " +" 0 as cbsCount " +" from cloud_host_info a " +" left join host_spec_price b " +" on a.host_spec = b.host_spec " +" group by label " +" order by hostPrice desc;",resultSetMapping = "cloudHostBudgetDto"
)
@SqlResultSetMapping(name = "cloudHostBudgetDto",entities = {@EntityResult(entityClass = CloudHostBudgetDTO.class,fields = {@FieldResult(name = "label", column = "label"),@FieldResult(name = "hostCount", column = "hostCount"),@FieldResult(name = "hostPrice", column = "hostPrice"),@FieldResult(name = "cbsPrice", column = "cbsPrice"),@FieldResult(name = "cbsCount", column = "cbsCount")})}
)
@Data
@Entity
@Immutable //注意这个注解,该 model 只为查询用,它本身的变更不会同步数据库表
public class CloudHostBudgetDTO {@Idprivate String label;private Integer hostCount;private Integer hostPrice;private Integer cbsPrice;private Integer cbsCount;
}
基于类的封装,我们用的是NamedNativeQuery和SqlResultSetMapping,虽然看着复杂点,但是其灵活性更好,可以预留多个额外字段,方便对有些时候从程序中动态计算结果的赋值,因为有些字段可能并不能从数据库里面全部计算好,所以还需要程序补充一部分,这个时候基于类的封装就很好了,你 Controller 方法里面拿到结果之后,可以继续遍历结果对某些字段进行额外的业务逻辑处理。
Immutable 注解解释
在 Hibernate 中,@Immutable 注解用于标识某个实体类是不可变的,即该实体的对象在持久化之后,其状态不应该被修改。这个注解告诉 Hibernate,不会对该实体进行任何更新或删除操作,比如上面我们的查询结果,就很明显只是为了接受聚合的结果用的,其 model 本身并不会对应数据库里面的任何一个实体表,所以这里必须用Immutable注解标记,明确告诉 Hibernate,当我们修改 model 字段属性时,不要去更新数据库里面的表,因为这个表并不存在,也不需要存在,如果你不加 Immutable 还会 model 属性进行了修改,那么这个时候就会报错,这一点需要注意。
@Immutable 的作用
优化性能
由于标记了 @Immutable 的实体对象不允许修改,Hibernate 在处理这些对象时可以进行一些性能上的优化。例如,Hibernate 不会再为这些对象生成更新语句(UPDATE),也不会尝试为它们执行任何删除操作。这样可以减少数据库交互的次数,从而提高性能,尤其在读取频繁且不修改的场景中。
数据一致性保证
@Immutable 可以确保实体对象的状态不被意外修改。如果有代码尝试对这些对象执行更新操作,Hibernate 会抛出异常。这样可以确保业务逻辑层的一致性,避免不小心对本应保持不变的数据进行修改。
与缓存结合
在 Hibernate 的一级缓存(Session 缓存)和二级缓存中,@Immutable 标记的对象可能会有不同的处理。Hibernate 可以在缓存中保留这些对象,并假定它们不会发生变化,从而减少了缓存失效的风险,提高了缓存命中率。