@Transactional 没有按预期工作,因为需要 "save" 方法保存到数据库
@Transactional does not work as expected, because the "save" method is needed to save to the database
@Transactional
本身应该反映对数据库中实体所做的更改。
我正在创建一个应用程序,客户端可以在其中创建一个 Car 实体,看起来像这样(update
方法后来被 PUT 使用,不要注意 brand
属性):
@Entity
@Table(name = "cars")
public class Car {
@Id
@GeneratedValue(generator = "inc")
@GenericGenerator(name = "inc", strategy = "increment")
private int id;
@NotBlank(message = "car name`s must be not empty")
private String name;
private LocalDateTime productionYear;
private boolean tested;
public Car() {
}
public Car(@NotBlank(message = "car name`s must be not empty") String name, LocalDateTime productionYear) {
this.name = name;
this.productionYear = productionYear;
}
@ManyToOne
@JoinColumn(name = "brand_id")
private Brand brand;
public int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDateTime getProductionYear() {
return productionYear;
}
public void setProductionYear(LocalDateTime productionYear) {
this.productionYear = productionYear;
}
public boolean isTested() {
return tested;
}
public void setTested(boolean tested) {
this.tested = tested;
}
public Brand getBrand() {
return brand;
}
void setBrand(Brand brand) {
this.brand = brand;
}
public Car update(final Car source) {
this.productionYear = source.productionYear;
this.brand = source.brand;
this.tested = source.tested;
this.name = source.name;
return this;
}
}
在我的应用程序中,客户端可以使用 PUT 方法创建新的 Car 或更新现有的 Car。
我的控制器:
@RestController
public class CarController {
private Logger logger = LoggerFactory.getLogger(CarController.class);
private CarRepository repository;
public CarController(CarRepository repository) {
this.repository = repository;
}
//The client can create a new resource or update an existing one via PUT
@Transactional
@PutMapping("/cars/{id}")
ResponseEntity<?> updateCar(@PathVariable int id, @Valid @RequestBody Car source) {
//update
if(repository.existsById(id)) {
repository.findById(id).ifPresent(car -> {
car.update(source); //it doesn`t work
//Snippet below works
//var updated = car.update(source);
//repository.save(updated);
});
return ResponseEntity.noContent().build();
}
//create
else {
var result = repository.save(source);
return ResponseEntity.created(URI.create("/" + id)).body(result);
}
}
}
当我创建一辆新汽车时,它可以正常工作。但是,如代码中所述,当没有保存方法时,尽管我得到状态 204(无内容),但实体不会更改。当有保存方法时,它工作正常。
你知道为什么会这样吗?
其中一位用户要求我提供 Brand
实体。到目前为止我还没有创建任何 Brand
对象,但本质上 Car
可以属于我的应用程序中的特定 Brand
。到目前为止,没有 Car
属于任何 Brand
。这是这个实体:
@Entity
@Table(name = "brands")
public class Brand {
@Id
@GeneratedValue(generator = "i")
@GenericGenerator(name = "i", strategy = "increment")
private int id;
@NotBlank(message = "brand name`s must be not empty")
private String name;
private LocalDateTime productionBrandYear;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "brand")
private Set<Car> cars;
@ManyToOne
@JoinColumn(name = "factory_id")
private Factory factory;
public Brand() {
}
public int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDateTime getProductionBrandYear() {
return productionBrandYear;
}
public void setProductionBrandYear(LocalDateTime productionBrandYear) {
this.productionBrandYear = productionBrandYear;
}
public Set<Car> getCars() {
return cars;
}
public void setCars(Set<Car> cars) {
this.cars = cars;
}
public Factory getFactory() {
return factory;
}
public void setFactory(Factory factory) {
this.factory = factory;
}
}
您正在更新汽车对象这一事实并不意味着它会更新数据库中的值。您总是需要调用 repository.save() 方法来将您的更改保存在数据库中。
我在本地用相同的用例尝试了您的实体,发现一切正常,我在这里写下我的发现和配置,以便您可以验证您的问题所在。
因此,当我发出 PUT 调用提供 id
但 Car
实体不存在于 table 中时,它被创建并且我收到 201 响应(我猜你是得到相同的)
您可以看到具有值的行也被插入到 table
这些是打印的查询日志
- [nio-8080-exec-8] org.hibernate.SQL: select count(*) as col_0_0_ from car car0_ where car0_.id=?
[nio-8080-exec-8] org.hibernate.SQL: select car0_.id as id1_1_0_, car0_.brand_id as brand_id5_1_0_, car0_.name as name2_1_0_, car0_.production_year as producti3_1_0_, car0_.tested as tested4_1_0_ from car car0_ where car0_.id=?
[nio-8080-exec-8] org.hibernate.SQL: insert into car (brand_id, name, production_year, tested) values (?, ?, ?, ?)
现在,让我们来更新同一个实体,当对具有更改值的相同 id
发出 PUT 请求时,请注意 table 中的值更改并更新日志中的查询
你可以看到同样的 204 响应是空的,让我们看看 table 条目
所以更改已反映在数据库中,让我们看看此操作的 SQL 日志
select count(*) as col_0_0_ from car car0_ where car0_.id=?
[nio-8080-exec-1] org.hibernate.SQL: select car0_.id as id1_1_0_, car0_.brand_id as brand_id5_1_0_, car0_.name as name2_1_0_, car0_.production_year as producti3_1_0_, car0_.tested as tested4_1_0_, brand1_.id as id1_0_1_, brand1_.name as name2_0_1_, brand1_.production_year as producti3_0_1_ from car car0_ left outer join brand brand1_ on car0_.brand_id=brand1_.id where car0_.id=?
[nio-8080-exec-1] org.hibernate.SQL: update car set brand_id=?, name=?, production_year=?, tested=? where id=?
所以,我不确定你是如何验证的以及你验证了什么,但你的实体必须工作,我使用了与你相同的控制器功能
@RestController
class CarController {
private final CarRepository repository;
public CarController(CarRepository repository) {
this.repository = repository;
}
@PutMapping("/car/{id}")
@Transactional
public ResponseEntity<?> updateCar(@PathVariable Integer id, @RequestBody Car source) {
if(repository.existsById(id)) {
repository.findById(id).ifPresent(car -> car.update(source));
return ResponseEntity.noContent().build();
}else {
Car created = repository.save(source);
return ResponseEntity.created(URI.create("/" + created.getId())).body(created);
}
}
}
与源代码的可能差异如下:
- 我使用
IDENTITY
生成器生成了 PRIMARY KEY,而不是你实体上的那个,因为我很容易测试它。
- 我向 serialize/deserialize 请求主体提供了 ObjectMapper bean 到
Car
对象以支持 Java 8 LocalDateTime
转换,您可能有发送日期时间值的方式,以便它转换为 Car
Object.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
// And Object mapper bean
@Bean
public static ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return mapper;
}
不过,这些差异应该无关紧要。
application.properties
打印查询日志以验证查询是否被触发
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.jpa.show-sql=true
spring.jpa.open-in-view=false
logging.level.org.hibernate.SQL=DEBUG
@Transactional
本身应该反映对数据库中实体所做的更改。
我正在创建一个应用程序,客户端可以在其中创建一个 Car 实体,看起来像这样(update
方法后来被 PUT 使用,不要注意 brand
属性):
@Entity
@Table(name = "cars")
public class Car {
@Id
@GeneratedValue(generator = "inc")
@GenericGenerator(name = "inc", strategy = "increment")
private int id;
@NotBlank(message = "car name`s must be not empty")
private String name;
private LocalDateTime productionYear;
private boolean tested;
public Car() {
}
public Car(@NotBlank(message = "car name`s must be not empty") String name, LocalDateTime productionYear) {
this.name = name;
this.productionYear = productionYear;
}
@ManyToOne
@JoinColumn(name = "brand_id")
private Brand brand;
public int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDateTime getProductionYear() {
return productionYear;
}
public void setProductionYear(LocalDateTime productionYear) {
this.productionYear = productionYear;
}
public boolean isTested() {
return tested;
}
public void setTested(boolean tested) {
this.tested = tested;
}
public Brand getBrand() {
return brand;
}
void setBrand(Brand brand) {
this.brand = brand;
}
public Car update(final Car source) {
this.productionYear = source.productionYear;
this.brand = source.brand;
this.tested = source.tested;
this.name = source.name;
return this;
}
}
在我的应用程序中,客户端可以使用 PUT 方法创建新的 Car 或更新现有的 Car。
我的控制器:
@RestController
public class CarController {
private Logger logger = LoggerFactory.getLogger(CarController.class);
private CarRepository repository;
public CarController(CarRepository repository) {
this.repository = repository;
}
//The client can create a new resource or update an existing one via PUT
@Transactional
@PutMapping("/cars/{id}")
ResponseEntity<?> updateCar(@PathVariable int id, @Valid @RequestBody Car source) {
//update
if(repository.existsById(id)) {
repository.findById(id).ifPresent(car -> {
car.update(source); //it doesn`t work
//Snippet below works
//var updated = car.update(source);
//repository.save(updated);
});
return ResponseEntity.noContent().build();
}
//create
else {
var result = repository.save(source);
return ResponseEntity.created(URI.create("/" + id)).body(result);
}
}
}
当我创建一辆新汽车时,它可以正常工作。但是,如代码中所述,当没有保存方法时,尽管我得到状态 204(无内容),但实体不会更改。当有保存方法时,它工作正常。 你知道为什么会这样吗?
其中一位用户要求我提供 Brand
实体。到目前为止我还没有创建任何 Brand
对象,但本质上 Car
可以属于我的应用程序中的特定 Brand
。到目前为止,没有 Car
属于任何 Brand
。这是这个实体:
@Entity
@Table(name = "brands")
public class Brand {
@Id
@GeneratedValue(generator = "i")
@GenericGenerator(name = "i", strategy = "increment")
private int id;
@NotBlank(message = "brand name`s must be not empty")
private String name;
private LocalDateTime productionBrandYear;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "brand")
private Set<Car> cars;
@ManyToOne
@JoinColumn(name = "factory_id")
private Factory factory;
public Brand() {
}
public int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDateTime getProductionBrandYear() {
return productionBrandYear;
}
public void setProductionBrandYear(LocalDateTime productionBrandYear) {
this.productionBrandYear = productionBrandYear;
}
public Set<Car> getCars() {
return cars;
}
public void setCars(Set<Car> cars) {
this.cars = cars;
}
public Factory getFactory() {
return factory;
}
public void setFactory(Factory factory) {
this.factory = factory;
}
}
您正在更新汽车对象这一事实并不意味着它会更新数据库中的值。您总是需要调用 repository.save() 方法来将您的更改保存在数据库中。
我在本地用相同的用例尝试了您的实体,发现一切正常,我在这里写下我的发现和配置,以便您可以验证您的问题所在。
因此,当我发出 PUT 调用提供 id
但 Car
实体不存在于 table 中时,它被创建并且我收到 201 响应(我猜你是得到相同的)
您可以看到具有值的行也被插入到 table
这些是打印的查询日志
- [nio-8080-exec-8] org.hibernate.SQL: select count(*) as col_0_0_ from car car0_ where car0_.id=?
[nio-8080-exec-8] org.hibernate.SQL: select car0_.id as id1_1_0_, car0_.brand_id as brand_id5_1_0_, car0_.name as name2_1_0_, car0_.production_year as producti3_1_0_, car0_.tested as tested4_1_0_ from car car0_ where car0_.id=?
[nio-8080-exec-8] org.hibernate.SQL: insert into car (brand_id, name, production_year, tested) values (?, ?, ?, ?)
现在,让我们来更新同一个实体,当对具有更改值的相同 id
发出 PUT 请求时,请注意 table 中的值更改并更新日志中的查询
你可以看到同样的 204 响应是空的,让我们看看 table 条目
所以更改已反映在数据库中,让我们看看此操作的 SQL 日志
select count(*) as col_0_0_ from car car0_ where car0_.id=?
[nio-8080-exec-1] org.hibernate.SQL: select car0_.id as id1_1_0_, car0_.brand_id as brand_id5_1_0_, car0_.name as name2_1_0_, car0_.production_year as producti3_1_0_, car0_.tested as tested4_1_0_, brand1_.id as id1_0_1_, brand1_.name as name2_0_1_, brand1_.production_year as producti3_0_1_ from car car0_ left outer join brand brand1_ on car0_.brand_id=brand1_.id where car0_.id=?
[nio-8080-exec-1] org.hibernate.SQL: update car set brand_id=?, name=?, production_year=?, tested=? where id=?
所以,我不确定你是如何验证的以及你验证了什么,但你的实体必须工作,我使用了与你相同的控制器功能
@RestController
class CarController {
private final CarRepository repository;
public CarController(CarRepository repository) {
this.repository = repository;
}
@PutMapping("/car/{id}")
@Transactional
public ResponseEntity<?> updateCar(@PathVariable Integer id, @RequestBody Car source) {
if(repository.existsById(id)) {
repository.findById(id).ifPresent(car -> car.update(source));
return ResponseEntity.noContent().build();
}else {
Car created = repository.save(source);
return ResponseEntity.created(URI.create("/" + created.getId())).body(created);
}
}
}
与源代码的可能差异如下:
- 我使用
IDENTITY
生成器生成了 PRIMARY KEY,而不是你实体上的那个,因为我很容易测试它。 - 我向 serialize/deserialize 请求主体提供了 ObjectMapper bean 到
Car
对象以支持 Java 8LocalDateTime
转换,您可能有发送日期时间值的方式,以便它转换为Car
Object.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
// And Object mapper bean
@Bean
public static ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return mapper;
}
不过,这些差异应该无关紧要。
application.properties 打印查询日志以验证查询是否被触发
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.jpa.show-sql=true
spring.jpa.open-in-view=false
logging.level.org.hibernate.SQL=DEBUG