@Transactional
should itself reflect the changes made to the entity in the database.
I'm creating an application where the client can create a Car entity that looks like this (the update
method is later used by PUT, do not pay attention to the brand
property):
@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;
}
}
In my application, the client can create a new Car or update an existing one with the PUT method.
My controller:
@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);
}
}
}
When I create a new Car, it works. However as described in the code, when there is no save method the entity is not changed although I get the status 204 (no content). When there is a save method, it works fine. Do you know why this is so?
One of the users asked me for a Brand
entity. I haven't created any Brand
object so far but essentially Car
can belong to a specific Brand
in my app. So far, no Car
belongs to any Brand
. Here is this entity:
@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;
}
}
I tried your entities with same use case locally and found out everything is working fine, I am writing here my findings and configurations so that you can verify what's going on wrong for you.
So, when I issue a PUT call providing id
but Car
entity doesn't exist into table, it gets created and I receive 201 response (I guess you are getting the same)
you can see that row with value got inserted into table as well
and these are the query logs printed
- [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 (?, ?, ?, ?)
Now, let's come to updating the same entity, when issued PUT request for same id
with changed values notice that values changes in table and update queries in log
You can see that got same 204 response with empty body, let's look the table entry
So changes got reflected in DB, let's look at the SQL logs for this operation
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=?
So, I am not sure, how you verified and what you verified but your entities must work, I have used same controller function as yours
@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);
}
}
}
Possible differences from your source code could be as follow:
IDENTITY
generator to generate the PRIMARY KEY, instead of the one you have on your entity as it was easy for me to test.Car
object to support Java 8 LocalDateTime
conversion, you may have your way to send datetime values, so that it converts to 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;
}
However, these differences should not matter.
application.properties To print query logs to verify if queries are fired or not
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