什么是JPA
JPA之于ORM(持久层框架,如MyBatis、Hibernate等)正如JDBC之于数据库驱动。
JDBC是Java语言定义的一套标准,规范了客户端程序访问关系数据库(如MySQL、Oracle、Postgres、SQLServer等)的应用程序接口,接口的具体实现(即数据库驱动)由各关系数据库自己实现。
随着业务系统的复杂,直接用JDBC访问数据库对开发者来说变得很繁琐,代码难以维护,为解决此问题,ORM(Object Relation Mapping)框架出现了,如MyBatis、Hibernate等,百花齐放。
爱大一统的Java又出手了,Java针对ORM提出了JPA,JPA 本质上是一种 ORM 规范,不是 ORM 框架,只是定制了一些规范,提供了一些编程的 API 接口,具体实现由 ORM 厂商实现,如Hiernate、Eclipselink等都是JAP的具体实现。
Spring Data JPA 其实并不依赖于 Spring 框架。
JPA注解
**@Entity**
@Entity 标注用于实体类声明语句之前,指出该Java 类为实体类,将映射到指定的关系数据库表。(类似的,使用@Document可以映射到mongodb)
**@Table**
当实体类与其映射的数据库表名不同名时需要使用 @Table 标注说明,该标注与 @Entity 标注并列使用
- schema属性:指定数据库名
- name属性:指定表名,不知道时表名为类名
**@IdClass**
修饰在实体类上,指定联合主键。如:@IdClass(StudentExperimentEntityPK.class),主键类StudentExperimentEntityPK需要满足:
- 实现Serializable接口
- 有默认的public无参数的构造方法
- 重写equals和hashCode方法。equals方法用于判断两个对象是否相同,EntityManger通过find方法来查找Entity时,是根据equals的返回值来判断的。hashCode方法返回当前对象的哈希码
**@id**
@Id 标注用于声明一个实体类的属性映射为数据库的一个主键列
@Id标注也可置于属性的getter方法之前。以下注解也一样可以标注于getter方法前。
若同时指定了下面的@GeneratedValue则存储时会自动生成主键值,否则在存入前用户需要手动为实体赋一个主键值。主键值类型可能是:
-
- Primitive types: boolean, byte, short, char, int, long, float, double.
- Equivalent wrapper classes from package java.lang:
Byte, Short, Character, Integer, Long, Float, Double. - java.math.BigInteger, java.math.BigDecimal.
- java.lang.String.
- java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp.
- Any enum type.
- Reference to an entity object.
- composite of several keys above
**@EmbeddedId**
功能与@IdClass一样用于指定联合主键。不同的在于其是修饰实体内的一个主键类变量,且主键类应该被@Embeddable修饰。
此外在主键类内指定的字段在实体类内可以不再指定,若再指定则需为@Column加上insertable = false, updatable = false属性
**@GeneratedValue**
@GeneratedValue 用于标注主键的生成策略,通过 strategy 属性指定。默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略:SqlServer 对应 identity,MySQL 对应 auto increment
- IDENTITY:采用数据库 ID自增长的方式来自增主键字段,Oracle 不支持这种方式
- AUTO: JPA自动选择合适的策略,是默认选项
- TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
- SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式
**@Basic**
表示一个简单的属性到数据表的字段的映射,对于没有任何标注的 getXxx() 方法,默认为 @Basic
fetch 表示属性的读取策略,有 EAGER 和 LAZY 两种,分别为主支抓取和延迟加载
optional 表示该属性是否允许为 null,默认为 true
**@Column**
当实体的属性与其映射的数据库表的列不同名时需要使用 @Column 标注说明,还有属性 unique、nullable、length 等
**@Transient**
表示该属性并非一个到数据库表的字段的映射,ORM 框架将忽略该属性
如果一个属性并非数据库表的字段映射,就务必将其标识为 @Transient,否则ORM 框架默认为其注解 @Basic,例如工具方法不需要映射
**@Temporal**
在 JavaAPI 中没有定义 Date 类型的精度,而在数据库中表示 Date 类型的数据类型有 Date,Time,TimeStamp 三种精度(日期,时间,两者兼具),进行属性映射的时候可以使用 @Temporal 注解调整精度。目前此注解只能用于修饰java.util.Date、java.util.Calendar类型的变量,TemporalType取DATE、TIME、TIMESTAMP时在MySQL中分别对应的DATE、TIME、DATETIME类型。示例:
@Temporal(TemporalType.TIMESTAMP) @UpdateTimestamp //org.hibernate.annotations.UpdateTimestamp,用于在保存Entity时自动更新该字段值,类似的还有@CreationTimestamp @Column(name = "update_time") private Date updateTime;
**@MappedSuperClass**
用来修饰一个类,类中声明了各Entity共有的字段,也即数据库中多表中共有的字段,如create_time、update_time、id等。
标注为@MappedSuperclass的类将不是一个完整的实体类,他将不会映射到数据库表,但是他的属性都将映射到其子类的数据库字段中。
标注为@MappedSuperclass的类不能再标注@Entity或@Table注解,也无需实现序列化接口。
SPA使用小记
JPA查询
基本用法:通过方法名来指定查询逻辑,而不需要自己实现查询的SQL逻辑,示例:List<Student> getByName(String name)
方法名解析原理:对方法名中除了保留字(findBy、top、within等)外的部分以and为分隔符提取出条件单词,然后解析条件获取各个单词并看是否和Entity中的属性对应(不区分大小写进行比较)。
条件解析原理:假设School和Student是一对多关系,Student中有个所属的School school字段、School有个String addressCode属性,以如下查询为例:
Studetn getByNameAndSchoolAddressCode(String studentName, String addressCode)(先说结果:JPA会自动生成条件studentName和关联条件student.school.addressCode进行查询)
- 由And分割得到studentName、SchoolAddressCode;
- 分别看Student中是否有上述两属性,显然前者有后者没有,则后者需要进一步解析(见下步)
- JPA按驼峰命名格式从后往前尝试分解SchoolAddressCode:先得到 [SchoolAdress、Code],由于Student没有SchoolAddress属性故继续尝试分解,得到[School、AdressCode];由于Student有School属性且School有addressCode属性故满足,最终得到条件student.school.addressCode。注:但若Student中有个SchoolAdress schoolAddress属性但schoolAddress中没有code属性,则会因找不到student.schoolAdress.code而报错,所以可通过下划线显示指定分割关系,即写成: getByNameAndSchool_AddressCode
在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下:
And --- 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd); Or --- 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr); Between --- 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min); LessThan --- 等价于 SQL 中的 "<",比如 findBySalaryLessThan(int max); GreaterThan --- 等价于 SQL 中的">",比如 findBySalaryGreaterThan(int min); IsNull --- 等价于 SQL 中的 "is null",比如 findByUsernameIsNull(); IsNotNull --- 等价于 SQL 中的 "is not null",比如 findByUsernameIsNotNull(); NotNull --- 与 IsNotNull 等价; Like --- 等价于 SQL 中的 "like",比如 findByUsernameLike(String user); NotLike --- 等价于 SQL 中的 "not like",比如 findByUsernameNotLike(String user); OrderBy --- 等价于 SQL 中的 "order by",比如 findByUsernameOrderBySalaryAsc(String user); Not --- 等价于 SQL 中的 "! =",比如 findByUsernameNot(String user); In --- 等价于 SQL 中的 "in",比如 findByUsernameIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数; NotIn --- 等价于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
Containing --- 包含指定字符串
StargingWith --- 以指定字符串开头
EndingWith --- 以指定字符串结尾
SPA List类型查询参数: List<StudentEntity> getByIdInAndSchoolId(List<String> studentIdList, String schoolId); ,关键在于 In 关键字。
Repository nativeQuery返回Entity:
使用nativeQuery时SQL语句查询的字段名若没as则是数据库中的字段名,如school_id,而API返回值通常是schoolId,可以在SQL里通过 school_id as schoolId取别名返回。然而若查询很多个字段值则得一个个通过as取别名,很麻烦,可以直接将返回值指定为数据库表对应的Entity,不过此法要求查询的是所有字段名,如:
@Query(value = " select t.* from teacher t where t.school_id=?1 "// 以下为搜索字段 + "and (?4 is NULL or name like %?4% or job_number like %?4% or bz like %?4% or phone like %?4% or email like %?4%) " + " order by job_number limit ?2, ?3 ", nativeQuery = true) List<TeacherEntity> myGetBySchoolIdOrderByJobNumber(String schoolId, int startIndex, Integer size, String searchNameOrJobnumOrBzOrPhoneOrEmai);// nativeQuery返回类型可以声明为Entity,会自动进行匹配,要求查回与Entitydb中字段对应的所有db中的字段
SPA分页或排序:可以在Repository的方法的最后加一个Sort 或者 Pageable 类型的参数,以便按规则进行排序或者分页查询(编译后会自动在语句后加order by或limit语句)。
Repository中的一个方法myGetByCourseIdAndStudentId:
@Query("select se from StudentExperimentEntity se where se.studentId= ?2 and se.experimentId in ( select e.id from ExperimentEntity e where e.courseId= ?1 ) ")
List<StudentExperimentEntity> myGetByCourseIdAndStudentId(String courseId, String studentId, Pageable pageable);//没有写上述@Query语句也可以加Pageable。虽然实际传值时传PageRequest对象,但若这里生命为PageRequest则不会分页,总是返回所有数据,why?
调用:
studentExperimentRepository.myGetByCourseIdAndStudentId(courseId, studentId, PageRequest.of(0, count, new Sort(Sort.Direction.DESC, "lastopertime")));
编译后会在myGetByCourseIdAndStudentId所写SQL后自动加上 order by studentexp0_.lastopertime desc limit ?
注:上述用法也支持nativeQuery,示例:
@Query(value = "select d.*, u.username from developer d inner join user u on d.id=u.id " + " where (?1 is null or d.nick_name like %?1% ) ", nativeQuery = true) List<DeveloperEntity> myGetByNicknameOrPhoneOrEmailOrBz(String searchNicknameOrPhoneOrEmailOrBz, Pageable pageable);
Repository中更新或创建并返回该Entity:如 UserEntity u=userRepository.save(userEntity) ,其中UserEntity包含成员变量private SchoolEntity schoolEntity。Repository的save方法会返回被save的entity,但若是第一次保存该entity(即新建一条记录)时u.schoolEntity的值会为null,解决:用saveAndFlush
返回Entity中的部分字段:
对于nativeQuery,直接select部分字段即可,结果默认会自动包装为Map。为了便于理解可以直接将结果声明为Map。示例:
@Query(value = "select g.id, g.school_id as schoolId, g.name, g.createtime, g.bz, count(s.id) as stuCount from grade g left join student s "
+ " on g.name=s.grade where g.school_id=(select a.school_id from admin a where a.id=?1)" // 以下为搜索条件
+ " and (?4 is null or g.name like %?4% or g.bz like %?4% ) "
+ " group by g.id limit ?2,?3", nativeQuery = true)
List<Map<String, Object>> myGetGradeList(String adminId, Integer page, Integer size,
String searchGradeNameOrGradeBz);
其可以达到目的,但缺点是sql里用的直接是数据库字段名,导致耦合大,数据库字段名一变,所有相关sql都得相应改变。
对于非nativeQuery:(sql里的字段名是entity的字段名,数据库字段名改动只要改变entity中对应属性的column name即可,解决上述耦合大的问题)
当Repository返回类型为XXEntity或List<XXEntity>时通常默认包含所有字段,若要去掉某些字段,可以去掉XXEntity中该字段的get方法。此法本质上还是查出来了只是spring在返回给调用者时去掉了。治标不治本。
也可以自定义一个bean,然后在Repository的sql中new该bean。此很死板,要求new时写bean的全限定名,比较麻烦。
更好的办法是与nativeQuery时类似直接在sql里select部分字段,不过非nativeQuery默认会将结果包装为List而不是Map,故不同的是:这里需要在sql里new map,此'map'非jdk里'Map';需要为字段名取别名,否则返回的Map里key为数值0、1、2... 。示例:
//为'map'不是'Map' @Query("select new map(g.name as name, count(s.id) as stuCount) from GradeEntity g, StudentEntity s where g.name=s.grade and g.schoolId=?1 group by g.id") List<Map<String, Object>> myGetBySchoolId(String schoolId);
SPA的update、delete:(需要加@Transactional、@Modefying)
@Transactional //也可以放在service方法上
@Modifying @Query("delete from EngineerServices es where es.engineerId = ?1")//update与此类似 int deleteByEgId(String engineerId);
对于delete操作,可以与query的写法类似,直接(注:update不支持这样写):
@Transactional @Modifying int deleteByEgId(String engineerId);
甚至更直接写为: int deleteByEgId(String engineerId); ,但此时记得需要在上层调用者部分添加@Transactional
SPA的count:
Integer countByName(String name);
级联操作(CASCADE):
Use of the cascade annotation element may be used to propagate the effect of an operation to associated entities. The cascade functionality is most typically used in parent-child relationships.
用于有依赖关系的实体间(@OneToMany、@ManyToOne、@OneToOne等)的级联操作:当对一个实体进行某种操作时,若该实体加了与该操作相关的级联标记,则该操作会传播到与该实体关联的实体(即对被级联标记的实体施加某种与级联标记对应的操作时,与该实体相关联的其他实体也会被施加该操作)。包括:
CascadeType.PERSIST:持久化,即保存
CascadeType.REMOVE:删除当前实体时,关联实体也将被删除
CascadeType.MERGE:更新或查询
CascadeType.REFRESH:级联刷新,即在保存前先更新别人的修改:如Order、Item被用户A、B同时读出做修改且B的先保存了,在A保存时会先更新Order、Item的信息再保存。
CascadeType.DETACH:级联脱离,如果你要删除一个实体,但是它有外键无法删除,你就需要这个级联权限了。它会撤销所有相关的外键关联。
CascadeType.ALL:上述所有
注:
级联应该标记在One的一方。如对于 @OneToMany的Person 和 @ManyToOne的Phone,若将CascadeType.REMOVE标记在Phone则删除Phone也会删除Person,显然是错的。
慎用CascadeType.ALL,应该根据业务需求选择所需的级联关系,否则可能酿成大祸。
延迟加载与立即加载(FetchType):通常可以在@OneToMany中用LAZY、在@ManyToOne/Many中用EAGER,但不绝对,看具体需要。
FetchType.LAZY:延迟加载,在查询实体A时,不查询出关联实体B,在调用getxxx方法时,才加载关联实体,但是注意,查询实体A时和getxxx必须在同一个Transaction中,不然会报错:no session
FetchType.EAGER:立即加载,在查询实体A时,也查询出关联的实体B
like查询
对于单字段的可以直接在方法名加Containing
@Query("select s from SchoolEntity s where s.customerId=?1 "// 以下为搜索条件
+ " and (?2 is null or s.name like %?2% or s.bz like %?2% ) ")
List<SchoolEntity> getByCustomerId(String customerId, String searchSchoolnameOrBz, Pageable pageable);
Entity中将任意对象映射为一个数据库字段:借助JPA converter to map your Entity to the database.
在要被映射的字段上加上注解: @Convert(converter = JpaConverterJson.class)
实现JpaConverterJson:
public class JpaConverterJson implements AttributeConverter<Object, String> {//or specialize the Object as your Column type private final static ObjectMapper objectMapper = new ObjectMapper(); @Override public String convertToDatabaseColumn(Object meta) { try { return objectMapper.writeValueAsString(meta); } catch (JsonProcessingException ex) { return null; // or throw an error } } @Override public Object convertToEntityAttribute(String dbData) { try { return objectMapper.readValue(dbData, Object.class); } catch (IOException ex) { // logger.error("Unexpected IOEx decoding json from database: " + dbData); return null; } } }
需要注意的是,若Entity字段是一个 JavaBean 或 JavaBean 列表(如 TimeSlice 或 List<TimeSlice> ),则反序列化时相应地会反序列化成 LinkedHashMap 或 List<LinkedHashMap>,故强转成TimeSlice或List<TimeSlice>虽然编译期不会报错但运行时就出现类型转换错误。故需要进一步转换成JavaBean,示例:
1 public static class TimeTableConverter implements AttributeConverter<List<TimeSlice>, String> {// or specialize the Object as your Column type 2 3 private final static ObjectMapper objectMapper = new ObjectMapper(); 4 5 @Override 6 public String convertToDatabaseColumn(List<TimeSlice> data) { 7 try { 8 return objectMapper.writeValueAsString(data); 9 } catch (JsonProcessingException ex) { 10 throw new ApiCustomException(ApiErrorCode.OTHER, "fail to convert list to string"); 11 } 12 } 13 14 @Override 15 public List<TimeSlice> convertToEntityAttribute(String dbData) { 16 // return objectMapper.readValue(dbData, List.class);//直接return会报ClassCastException 17 18 Field[] fields = TimeSlice.class.getDeclaredFields(); 19 20 try { 21 List<Map<String, Object>> tmpMapList = objectMapper.readValue(dbData, List.class);// 对于键值对类型元素,默认反序列化成LinkedListMap类型,故需进一步转换成TimeSlice 22 List<TimeSlice> timeSliceList = null; 23 if (null != tmpMapList) { 24 timeSliceList = new ArrayList<>(); 25 for (Map<String, Object> map : tmpMapList) { 26 TimeSlice tmpTimeSlice = new TimeSlice(); 27 timeSliceList.add(tmpTimeSlice); 28 for (Field field : fields) {// 复制出所有属性 29 try { 30 field.setAccessible(true); 31 field.set(tmpTimeSlice, map.get(field.getName())); 32 } catch (IllegalArgumentException | IllegalAccessException e) { 33 e.printStackTrace(); 34 } 35 } 36 } 37 } 38 return timeSliceList; 39 } catch (IOException ex) { 40 throw new ApiCustomException(ApiErrorCode.OTHER, "fail to convert string to list"); 41 } 42 } 43 }
参考资料:https://stackoverflow.com/questions/25738569/jpa-map-json-column-to-java-object
将任意非基本数据类型(如java bean、list等)对应到数据库字段:
本质上就是将数据序列化成基本数据类型如String。如要把List<String> gradeIdList对应到数据库中的字符串类型的courseSchedule字段。
法1:可以在业务层写代码将gradeIdList序列化成String: String res=objectMapper.writeValueAsString(gradeIdList);// 借助objectMapper.writeValueAsString(data); ,之后保存即可。从数据库中读取时: List<String> gradeIdList=objectMapper.readValue(dbData, List<String>.class); 。此法可以解决问题,但每个字段都得自己手动写此过程。
法2:实现一个AttributeConverter,并应用于Entity字段。此法相当于指定了AttributeConverter后让框架去自动做转换
@Column(name = "course_schedule") @Convert(converter = MyJpaConverterJson.class) private List<String> courseSchedule; public class MyJpaConverterJson implements AttributeConverter<List<String>, String> {// or specialize the Object as your // Column type private final static ObjectMapper objectMapper = new ObjectMapper(); @Override public String convertToDatabaseColumn(List<String> data) { try { return objectMapper.writeValueAsString(data); } catch (JsonProcessingException ex) { throw new ApiCustomException(ApiErrorCode.OTHER, "fail to convert list to string"); } } @Override public List<String> convertToEntityAttribute(String dbData) { try { return objectMapper.readValue(dbData, List.class); } catch (IOException ex) { throw new ApiCustomException(ApiErrorCode.OTHER, "fail to convert string to list"); } } }
枚举示例
@Column(name = "sex") @Enumerated(EnumType.ORDINAL)//持久化为0,1 private Sex sex; @Column(name = "type") @Enumerated(EnumType.STRING)//持久化为字符串 private Role role;
复杂条件(多条件和多表)查询和分页:Specification
示例:
@Override public Page<CourseDeveloperEntity> listJoinedDevelopersOfGivenCourse(Integer page, Integer size, String courseId, String optionalExcludedDeveloperId, String optionalEmailOrNickName) { PageRequest pageRequest = PageRequest.of(page, size, Sort.by(Sort.Order.desc("developer.nickName"), Sort.Order.asc("role"))); return courseDeveloperRepository.findAll((Specification<CourseDeveloperEntity>) (root, query, criteriaBuilder) -> { List<Predicate> predicateList = new ArrayList<Predicate>(); predicateList.add(criteriaBuilder.equal(root.get("course").get("id"), courseId)); if (null != optionalExcludedDeveloperId) {// 排除指定开发者 predicateList.add(criteriaBuilder.notEqual(root.get("developer").get("id"), optionalExcludedDeveloperId)); } if (null != optionalEmailOrNickName && optionalEmailOrNickName.trim().length() > 0) { // Predicate p1 = criteriaBuilder.equal(root.get("developer").get("nickName"), optionalEmailOrNickName); // Predicate p2 = criteriaBuilder.equal(root.get("developer").get("email"), optionalEmailOrNickName); Predicate p1 = criteriaBuilder.like(root.get("developer").get("nickName"), "%" + optionalEmailOrNickName + "%"); Predicate p2 = criteriaBuilder.like(root.get("developer").get("email"), "%" + optionalEmailOrNickName + "%"); predicateList.add(criteriaBuilder.or(p1, p2)); } return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()])); }, pageRequest); }
JPA Repository的save(xxx)方法
通过show-sql=true参数打印sql语句,可以发现其内部先是按被保存的Entity的主键查出该Entity,若存在则更新并保存、否则插入。
save方法以Entity为参数用于保存。其效果可能是插入新数据或修改已有数据,在执行时会根据Entity参数自动判断:如果该Entity参数中的主键值(可能是联合主键)在DB中已存在则是更新、否则为插入。因此,在某些场景下会有问题。
举个例子,我们有这么个场景:Entity为CourseDeveloperEntity,该Entity中有单独的id字段作为主键、且有course_id、developer_id联合唯一索引、还有个is_delete用于逻辑删除。
在该场景下,若我们删除了一个CourseDeveloperEntity,则该Entity被删除了(is_delete标记为true以逻辑删除,数据库中实际上还在,但上层业务查不到),因此若之后再插入(course_id、developer_id)一样的Entity但没指定id一样,则会因违背联合唯一索引而duplicate key的错。相关代码:
// 保存 CourseDeveloperEntity courseDeveloperEntity = courseDeveloperRepository.getByCourseIdAndDeveloperId(courseId, developerEntity.getId());//查不到被逻辑删除的记录 if (null == courseDeveloperEntity) { courseDeveloperEntity = new CourseDeveloperEntity(); } courseDeveloperEntity.setCourseId(courseId); courseDeveloperEntity.setDeveloperId(developerEntity.getId()); courseDeveloperEntity.setRole(developerRoleInTheCourse); return courseDeveloperRepository.save(courseDeveloperEntity);//由于主键自动新生成故courseDeveloperEntity被当成新的,故是执行insert而不是update,从而报错
JPA Repository的删除操作
方法名包含条件的删除操作,如 Integer deleteByNameAndSId(String name, String uuid); ,其执行时与save类似,也是先根据条件查出目标Entity再执行删除操作。对于 void delete(T entity); 则直接根据Entity的主键操作而不用先查。
逻辑删除
借助org.hibernate.annotations旳 @Where、@SQLDelete、@SQLDeleteALL 这三个注解来实现。
1、定义一个字段用于标识记录是否被逻辑删除。这里通过JPA的@MappedSuperclass定义各Entity共有的字段(该注解修饰的Entity不会对应数据库表,但其内定义的字段会被继承该Entity的子Entity对应到数据库字段),包含is_delete:
@Data @NoArgsConstructor @MappedSuperclass public abstract class BaseEntity { @Setter(value = AccessLevel.PRIVATE) @Temporal(TemporalType.TIMESTAMP) @CreationTimestamp @Column(name = "create_time", nullable = false) private Date createTime; @Setter(value = AccessLevel.PRIVATE) @Temporal(TemporalType.TIMESTAMP) @UpdateTimestamp @Column(name = "update_time", nullable = false) private Date updateTime; @Getter(value = AccessLevel.PRIVATE) @Setter(value = AccessLevel.PRIVATE) @Column(name = constant.ISDELETE_COLUMN_NAME, nullable = false) private Boolean isDelete = false; }
2、通过@Where、@SQLDelete、@SQLDeleteALL 三个注解修饰对应数据库表的Entity来实现逻辑删除:
@SQLDelete(sql = "update " + StudentEntity.tableName + " set " + constant.ISDELETE_COLUMN_NAME + " =true where sid=?") // 对非nativeQuery 旳delete起作用,包括形如deleteByName等,下同 @SQLDeleteAll(sql = "update " + StudentEntity.tableName + " set " + constant.ISDELETE_COLUMN_NAME + " =true where sid=?") @Where(clause = constant.ISDELETE_COLUMN_NAME + " = false") // 对非nativeQuery的select起作用(如count、非nativeQuery的String myGetNameByName等,前者本质上也是select) @Data @Entity @Table(name = StudentEntity.tableName) public class StudentEntity extends BaseEntity { public static final String tableName = "student"; @Id @Column(name = "sid", length = 36) private String sId; @Column(name = "name", length = 36) private String name; @Column(name = "age") private Integer age; }
需要注意的是:
- @Where会自动在查询语句后拼接@Where中指定的条件;该注解对所有的非nativeQuery的查询其作用,如getByName、count、自己写查询语句但非nativeQuery的myGetByName等。
- @SQLDelete会自动将删除语句替换为@SQLDelete中指定的sql操作;该注解对所有非nativeQuery的删除操作其作用,如delete(StudenEntity entity)、deleteBySId、deleteByName等,但由于指定的sql操作中条件不得不写死,所以要按期望起作用的话@SQLDelete中的sql操作应以Entity的主键为条件,且删除语句按上述前两者写法写(对于delete(StudenEntity entity)会自动取entity的主键给sid),而不能用第三种(会将name参数值传给sid)
更多参考资料:
本文介绍了JPA相关知识。JPA是Java针对ORM提出的规范,有多种注解用于映射数据库表和字段。还阐述了JPA查询、分页、排序、更新、删除等操作的使用方法,以及级联操作、延迟加载等特性,同时提及逻辑删除的实现方式。

903

被折叠的 条评论
为什么被折叠?



