@transactional不能按预期工作,因为需要使用“save”方法来保存到数据库

lhcgjxsq  于 2021-07-13  发布在  Java
关注(0)|答案(2)|浏览(374)

@Transactional 它本身应该反映对数据库中的实体所做的更改。我正在创建一个应用程序,客户机可以在其中创建一个像这样的car实体( update 方法是以后用的放,不注意了 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方法创建一辆新车或更新一辆现有的车。
我的控制器:

@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);
        }
    }
}

当我创造一辆新车时,它是有效的。但是,如代码中所述,当没有save方法时,尽管我得到了状态204(无内容),但实体没有更改。如果有save方法,它就可以正常工作。你知道为什么会这样吗?
其中一个用户向我请求 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;
    }
}
ttcibm8c

ttcibm8c1#

我在本地用相同的用例测试了您的实体,发现一切都正常工作,我在这里写下我的发现和配置,这样您就可以验证您出了什么问题。
所以,当我发出put呼叫时 id 但是 Car 实体不存在于表中,它被创建并且我收到201响应(我猜您得到的是相同的)

您可以看到值为的行也被插入到表中

这些是打印出来的查询日志

- [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 (?, ?, ?, ?)

现在,让我们来更新相同的实体,当发出相同的put请求时 id 对于更改的值,请注意表中的值将更改,并更新日志中的查询

您可以看到,对于空的body得到了相同的204响应,让我们看看表条目

因此,这些更改反映在db中,让我们看看此操作的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 生成器来生成主键,而不是实体上的主键,因为这对我来说很容易测试。
我提供了objectmapperbean来序列化/反序列化请求主体 Car 对象以支持Java8 LocalDateTime 转换时,您可能有发送日期时间值的方法,以便它转换为 Car 对象。

@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
qaxu7uf2

qaxu7uf22#

更新car对象并不意味着它会更新db中的值。您始终需要调用repository.save()方法将更改持久化到数据库中。

相关问题