I've these two simple entities Something
and Property
. The Something
entity has a many-to-one relationship to Property
, so when I create a new Something
row, I assign an existing Property
.
Something:
@Entity
@Table(name = "something")
public class Something implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "owner")
private String owner;
@ManyToOne
private Property property;
// getters and setters
@Override
public String toString() {
return "Something{" +
"id=" + getId() +
", name='" + getName() + "'" +
", owner='" + getOwner() + "'" +
", property=" + getProperty() +
"}";
}
Property:
@Entity
@Table(name = "property")
public class Property implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "shape")
private String shape;
@Column(name = "color")
private String color;
@Column(name = "dimension")
private Integer dimension;
// getters and setters
@Override
public String toString() {
return "Property{" +
"id=" + getId() +
", shape='" + getShape() + "'" +
", color='" + getColor() + "'" +
", dimension='" + getDimension() + "'" +
"}";
}
}
This is the SomethingRepository
(Spring):
@SuppressWarnings("unused")
@Repository
public interface SomethingRepository extends JpaRepository<Something,Long> {
}
Through a REST controller and a JSON, I want to create a new Something
:
@RestController
@RequestMapping("/api")
public class SomethingResource {
private final SomethingRepository somethingRepository;
public SomethingResource(SomethingRepository somethingRepository) {
this.somethingRepository = somethingRepository;
}
@PostMapping("/somethings")
public Something createSomething(@RequestBody Something something) throws URISyntaxException {
Something result = somethingRepository.save(something);
return result;
}
}
This is the JSON in input (the property
id
1 is an existing row in the database):
{
"name": "MyName",
"owner": "MySelf",
"property": {
"id": 1
}
}
The problem is: after the method .save(something)
, the variable result
contains the persisted entity, but without the fields of field property
, validated (they are null
):
Output JSON:
{
"id": 1,
"name": "MyName",
"owner": "MySelf",
"property": {
"id": 1,
"shape": null,
"color": null,
"dimension": null
}
}
I expect that they are validated/returned after the save operation.
To workaround this, I have to inject/declare the EntityManager
in the REST controller, and call the method EntityManager.refresh(something)
(or I have to call a .findOne(something.getId())
method to have the complete persisted entity):
@RestController
@RequestMapping("/api")
@Transactional
public class SomethingResource {
private final SomethingRepository somethingRepository;
private final EntityManager em;
public SomethingResource(SomethingRepository somethingRepository, EntityManager em) {
this.somethingRepository = somethingRepository;
this.em = em;
}
@PostMapping("/somethings")
public Something createSomething(@RequestBody Something something) throws URISyntaxException {
Something result = somethingRepository.save(something);
em.refresh(result);
return result;
}
}
With this workaround, I've the expected saved entith (with a correct JSON):
{
"id": 4,
"name": "MyName",
"owner": "MySelf",
"property": {
"id": 1,
"shape": "Rectangle",
"color": "Red",
"dimension": 50
}
}
Is there an automatic method/annotation, with JPA or Spring or Hibernate, in order to have the "complete" persisted entity?
I would like to avoid to declare the EntityManager
in every REST or Service class, or I want avoid to call the .findOne(Long)
method everytime I want the new refreshed persisted entity.
Instead of defining EntityManager
in each of your resource, you can define it once by creating a Custom JpaRepository. Reference
Then use the refresh
of your EntityManager
in each of your repository directly.
Refer the below example:
CustomRepository Interface
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import java.io.Serializable;
@NoRepositoryBean
public interface CustomRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
void refresh(T t);
}
CustomRepository Implementation
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import java.io.Serializable;
public class CustomRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
implements CustomRepository<T, ID> {
private final EntityManager entityManager;
public CustomRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
@Override
@Transactional
public void refresh(T t) {
entityManager.refresh(t);
}
}
Enable Custom JPARepository in Spring Boot Application Class
@SpringBootApplication
@EnableJpaRepositories (repositoryBaseClass = CustomRepositoryImpl.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Your Something Repository
public interface SomethingRepository extends CustomRepository<Something, Long> {
}
Use Refresh directly in SomethingResource (Assuming Something is an Entity)
@RestController
@RequestMapping("/api")
@Transactional
public class SomethingResource {
private final SomethingRepository somethingRepository;
public SomethingResource(SomethingRepository somethingRepository) {
this.somethingRepository = somethingRepository;
}
@PostMapping("/somethings")
public Something createSomething(@RequestBody Something something) throws URISyntaxException {
Something result = somethingRepository.save(something);
somethingRepository.refresh(result);
return result;
}
}
That's not enough:
Something result = somethingRepository.save(something);
You need to manually merge the incoming entity:
Something dbSomething = somethingRepository.findOne(
Something.class, something.getId()
);
dbSomething.setName(something.getName());
dbSomething.setOwner(something.getOwner());
somethingRepository.save(dbSomething);
Since the property
attribute is using the default FetchType.EAGER
, the entity should have the property
attribute initialized.
But, that's strange to call the Repository twice from the REST controller. You should have a Service layer that does all that in a @Transactional
service method. That way, you don't need to resave the entity since it's already managed.
@Transactional
public Something mergeSomething(Something something) {
Something dbSomething = somethingRepository.findOne(
Something.class, something.getId()
);
dbSomething.setName(something.getName());
dbSomething.setOwner(something.getOwner());
return dbSomething;
}
Now, you need to carefully merge every property you sent. In your case, if you send null
for property
you should decide whether you should nullify the @ManyToOne
reference or not. So, it depends on your current application business logic requirements.
Update
If you make sure you always send back the same entity you previously fetched, you could just use merge
.
em.merge(result);
But your property
attribute is just an id, and not an actual child entity, so you have to resolve that yourself in the Service layer.
property
field (and its fields). I tried to move the save operation in a @Service
class and in a method marked as @Transactional
, but nothing change. I don't understand why Spring and Hibernate don't retrieve and don't attach the fields to property
field. Only the id
. In the input JSON I don't need and don't want to specify their value. I want a reload from DB.
In Spring Boot JpaRepository:
If our modifying query changes entities contained in the persistence context, then this context becomes outdated.
In order to fetch the entities from the database with latest record.
Use @Modifying(clearAutomatically = true)
@Modifying annotation has clearAutomatically attribute which defines whether it should clear the underlying persistence context after executing the modifying query.
Example:
@Modifying(clearAutomatically = true)
@Query("UPDATE NetworkEntity n SET n.network_status = :network_status WHERE n.network_id = :network_id")
int expireNetwork(@Param("network_id") Integer network_id, @Param("network_status") String network_status);
flushAutomatically = true
. See stackoverflow.com/a/57062549/71066 for more info.
By the time you persist the entity it will be in managed state so if you just call something.getProperty();
it loads from the database and fills the property
value of the something
entity
public Something save(Something something) {
em.persist(something);
something.getProperty();
return something;
}
so normally when you have many-to-one relationship that should be fetched automatically. If not calling the getters of the objects in entity will fill them too by firing a new DB Find request.
Success story sharing
CustomRepository
is an interface not a class in your code.Something
, can you explain me why