JPA @Version 以及mysql bit 类型映射java boolean类型 问题解决详情_20211127
问题
最近在编程中遇到一个问题,由于个人之前未使用过JPA(用的都是mybatis),困惑了几天,今天心血来潮解决问题(之前有猜想是哪方面导致的,印证下猜想)。
问题描述:在某一个场景(如:新增用户)下,项目架构采用的是mysql、JPA技术,在新增用户持久化到数据库时,发现status、version这两个字段明明设置了default 然而并没有持久化到数据库
问题样例代码
Pom.xml
有mysql、springboot、test、mapstruct、jpa、lombok、log4f2
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.eleven</groupId>
<artifactId>jpa-study</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jpa-study</name>
<description>jpa-study</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
<version>1.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.6</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
数据库表设计
可以明显看出来 status、version 是设置了default
CREATE TABLE `summary` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`year` varchar(4) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '年',
`price` decimal(10, 2) NULL DEFAULT NULL COMMENT '金额',
`status` bit(1) NULL DEFAULT b'0' COMMENT '状态',
`remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
`version` int(11) NULL DEFAULT 0 COMMENT '版本',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
yml
注意 此处url、username、password 省略
spring:
datasource:
#url: jdbc:mysql://......
username: ......
password: ......
#driver-class-name: com.mysql.cj.jdbc.Driver
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
url: jdbc:log4jdbc:mysql://......
jpa:
hibernate:
ddl-auto: none
show-sql: true
实体类
注意:@version 在方法上 status是Boolean类型
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
/**
* @author Administrator
*/
@MappedSuperclass
public abstract class BaseObject implements Cloneable{
protected Boolean status;
protected String remark;
protected Integer version;
public Boolean getStatus() {
return status;
}
public void setStatus(Boolean status) {
this.status = status;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Version
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
import com.eleven.jpa.study.entity.base.BaseObject;
import lombok.Data;
import javax.persistence.*;
import java.math.BigDecimal;
/**
* @author Administrator
*/
@Data
@Entity
@Table(name = "summary")
public class Summary extends BaseObject {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String year;
private BigDecimal price;
}
import com.eleven.jpa.study.entity.base.BaseObject;
import lombok.Data;
import java.math.BigDecimal;
/**
* @author Administrator
*/
@Data
public class SummaryVo extends BaseObject {
private Integer id;
private String year;
private BigDecimal price;
}
dao
/**
* @author Administrator
*/
@Repository
public interface SummaryRepository extends JpaRepository<Summary, Integer> {
/**
* get by year
* @return
*/
Summary getByYear(String year);
/**
* add batch
* @param summaryVos
*/
void addBatch(List<SummaryVo> summaryVos);
}
import com.eleven.jpa.study.convert.SummaryConvert;
import com.eleven.jpa.study.entity.Summary;
import com.eleven.jpa.study.vo.SummaryVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import java.util.List;
/**
* @author Administrator
*/
public class SummaryRepositoryImpl{
@Autowired
private EntityManager entityManager;
@Transactional(rollbackFor = Exception.class)
public void addBatch(List<SummaryVo> summaryVos) {
summaryVos.stream().forEach(summaryVo -> {
Summary summary = SummaryConvert.INSTANCE.voToDo(summaryVo);
entityManager.persist(summary);
});
entityManager.flush();
entityManager.clear();
}
}
convert
import com.eleven.jpa.study.entity.Summary;
import com.eleven.jpa.study.vo.SummaryVo;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author Administrator
*/
@Mapper(componentModel = "spring")
public interface SummaryConvert {
SummaryConvert INSTANCE = Mappers.getMapper(SummaryConvert.class);
/**
* do to Vo
* @param summary
* @return
*/
SummaryVo doToVo(Summary summary);
/**
* vo to do
* @param summaryVo
* @return
*/
Summary voToDo(SummaryVo summaryVo);
}
Service
import com.eleven.jpa.study.vo.SummaryVo;
import java.util.List;
/**
* @author Administrator
*/
public interface ISummaryService {
/**
* get by year
* @return
*/
SummaryVo getByYear();
/**
* 批量添加
* @param summaryVos
*/
void addSummaryList(List<SummaryVo> summaryVos);
/**
* 添加
* @param summaryVo
*/
void addSummary(SummaryVo summaryVo);
}
import com.eleven.jpa.study.convert.SummaryConvert;
import com.eleven.jpa.study.dao.SummaryRepository;
import com.eleven.jpa.study.service.ISummaryService;
import com.eleven.jpa.study.vo.SummaryVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* @author Administrator
*/
@Service
public class ISummaryServiceImpl implements ISummaryService {
@Autowired
private SummaryRepository summaryRepository;
@Override
public SummaryVo getByYear() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy");
LocalDate now= LocalDate.now();
String year = now.format(formatter);
return SummaryConvert.INSTANCE.doToVo(summaryRepository.getByYear(year));
}
@Override
public void addSummaryList(List<SummaryVo> summaryVos) {
summaryRepository.addBatch(summaryVos);
}
@Override
public void addSummary(SummaryVo summaryVo) {
summaryRepository.save(SummaryConvert.INSTANCE.voToDo(summaryVo));
}
}
Test
@SpringBootTest
class JpaStudyApplicationTests {
@Autowired
private ISummaryService summaryService;
@Test
void contextLoads() {
List<SummaryVo> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
SummaryVo summaryVo = new SummaryVo();
summaryVo.setPrice(new BigDecimal(10));
summaryVo.setYear("2021");
list.add(summaryVo);
}
summaryService.addSummaryList(list);
}
@Test
void addSummary(){
SummaryVo summaryVo = new SummaryVo();
summaryVo.setYear("2021");
summaryVo.setPrice(new BigDecimal(10));
summaryService.addSummary(summaryVo);
}
}
执行结果
一次新增还是批量新增 status、version 值
问题分析
id注解在子类的方法上,version注解在父类的字段上
代码调试追踪
InheritanceState.determineDefaultAccessType方法先反射方法调用JavaXClass.getDeclaredProperties 根据id属性获取到注解id,所以返回accessType Property
在后面进行字段绑定
因为accessType 是 property 但是 我所声明的version注解是在字段上(field)
在获取version注解时 获取不到
结论
id注解的作用域会影响version注解的作用域,所以建议 要么都在字段要么都在方法上
解决问题调整代码
将BaseObject类调整status version 调整后的样子
import lombok.Data;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
/**
* @author Administrator
*/
@Data
@MappedSuperclass
public abstract class BaseObject implements Cloneable{
protected boolean status;
protected String remark;
@Version
protected Integer version;
}
一次新增执行结果
批量新增执行结果