@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 调用提供 idCar 实体不存在于 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