I tried almost all what I could find here in SO, and another sites tutorials about creating an One to One Relationship with Hibernate.
So, I have two models, here are the last modifications, like for example the #MapsId annotation I also removed in previous test.
Usuario:
#Entity
#Table(name="usuarios")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Usuario {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="usuarios_id_seq")
#SequenceGenerator(name="usuarios_id_seq", sequenceName="usuarios_id_seq", allocationSize=1)
#Column(name="id")
private Long id;
#ManyToOne
#JoinTable(name="roles_usuarios", joinColumns={#JoinColumn(name="usuarios_id", referencedColumnName="id")}, inverseJoinColumns={#JoinColumn(name="roles_id", referencedColumnName="id")})
private Rol rol;
#OneToOne(cascade = CascadeType.ALL, mappedBy="usuario")
private Cliente cliente;
Cliente:
#Entity
#Table(name="clientes")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Cliente {
#Id
//#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="clientes_id_seq")
//#SequenceGenerator(name="clientes_id_seq", sequenceName="clientes_id_seq", allocationSize=1)
//#Column(name="id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="usuario_id", referencedColumnName="id")
#MapsId
private Usuario usuario;
Cliente Controller:
#PostMapping("/")
public ResponseEntity<Void> postCliente(#RequestBody Cliente cliente, UriComponentsBuilder ucBuilder) {
if( clienteService.isClienteExist(cliente) ){
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
}
clienteService.save(cliente);
HttpHeaders headers = new HttpHeaders();
headers.setLocation( ucBuilder.path("/{id}").buildAndExpand(cliente.getId()).toUri() );
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
Cliente Service:
#Override
public Cliente save(Cliente cliente) {
Cliente clt = new Cliente();
clt.setUsuario(cliente.getUsuario());
clt.setRazonSocial(cliente.getRazonSocial());
clt.setRfc(cliente.getRfc());
clt.setDireccion(cliente.getDireccion());
clt.setEmail(cliente.getEmail());
clt.setTelefono(cliente.getTelefono());
clt.setContacto(cliente.getContacto());
clt.setTelContacto(cliente.getTelContacto());
clt.setEmailContacto(cliente.getEmailContacto());
return clienteRepository.save(clt);
}
If you notice I also have a many to one relationship with a Rol table which works fine, but when I pass information in the OneToOne which I pass it as a JSON it produces: detached entity passed to persist: com.swargos.entities.Usuario
IDK if I'm missing some annotations, or is that the database is created when running the spring application.
I'm providing a somewhat qualified guess, since you didn't include code that shows how you call persist.
The error means that the Usuario instance you are passing to persist() already has a primary key, but it is not a managed entity of that persistence context, see here for Entity Object lifecycle
My guess is that the Usuario instance was loaded by another EntityManager, then json-serialized to the front-end, and then posted back to the backend, and you need to set it on a Cliente (Since you have cascade in both directions it may also be the Cliente being set on the Usuario). Every time an entity has been loaded in one Persistence Context, and you want to save it in another you must either call em.merge() or you must call em.find() to load it into it (and then set the changes).
JPA is not magic, the life-cycle of the Entities and the Persistence Context which manage them is well defined, and unless the developer understands how these mechanisms work, a lot of time will be wasted trying to work against the framework.
Also #MapsId should only be used if Cliente used an #EmbeddedId for it primary key, which does not seem to be the case.
Related
I have a Unit entity and I'm using the #LastModifiedDate annotation to keep track of the updates. The problem is that in case I only update the items field the updateDate field isn't updated with the new date but if I update any other fields in the Unit entity the updateDate field is updated properly.
//other annotations
#EntityListeners(AuditingEntityListener.class)
public class Unit {
#Id
#GeneratedValue
private UUID id;
private String unitId;
private String unitName;
//other fields
#LastModifiedDate
private LocalDateTime updateDate;
#OneToMany(cascade = {CascadeType.ALL, CascadeType.REFRESH})
#OrderBy("slotNumber")
private List<Item> items;
}
Unit repository
public interface UnitRepo extends CrudRepository<Unit, String> {
Set<Unit> findAllByProfileUsername(String username);
}
And my update method in my Unit service
public Unit updateUnit(Unit unit) {
return repo.save(unit);
}
#LastModifiedDate will only update the modified date when the changes have been made in the entity parameters, not the relationship. When you only modify the Item, updateDate will not be updated. You may find the open issue(for mongo) related to the same.
In case you want to modify the updateDate, you may implement the entity listeners with the #PrePersist or #PreUpdate (see JPA Lifecycle). You may also have a look into AuditorAware
You can use the callback methods in your Unit Entity class.which allows to detect the changes before / after to the entity class. #PreUodate and #PostUpdate you can try.
References -
https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/listeners.html
I have a simple straight forward demo application with spring-boot, spring-data-jpa and a h2-DB.
I have build two entities which are mapped by an OneToOne relationship.
Post.java
#Entity
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToOne(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private PostDetail postDetail;
}
PostDetail.java
#Entity
public class PostDetail {
#Id
#GeneratedValue
private Long id;
private String message;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
#JoinColumn(name = "id")
private Post post;
}
I try to create and save a new Post. Then I try to create a new PostDetail, set the previous generated Post to it and save it. In the one controller sample I dont have a #Transactional annotation and in the seconde sample I do annotate the method with #Transactional
#RestController
public class TestController {
#Autowired
PostRepository postRepository;
#Autowired
PostDetailRepository postDetailRepository;
#GetMapping("/test1")
public String test1() {
Post post = new Post();
post.setId(2L);
post.setTitle("Post 1");
postRepository.save(post);
PostDetail detail = new PostDetail();
detail.setMessage("Detail 1");
detail.setPost(post);
postDetailRepository.save(detail);
return "";
}
#Transactional
#GetMapping("/test2")
public String test2() {
Post post = new Post();
post.setId(2L);
post.setTitle("Post 1");
postRepository.save(post);
PostDetail detail = new PostDetail();
detail.setMessage("Detail 1");
detail.setPost(post);
postDetailRepository.save(detail);
return "";
}
}
Why do I get in the first sample a org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.demo.jpa.model.Post exception and in the other sample not?
Can anyone explain why this happens?
You use bidirectional #OneToOne association. As hibernate documentation states:
Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times.
So, you should rewrite your test method in this way:
#GetMapping("/test1")
public String test1() {
Post post = new Post();
post.setId(2L);
post.setTitle("Post 1");
PostDetail detail = new PostDetail();
detail.setMessage("Detail 1");
// synchronization of both sides of #OneToOne association
detail.setPost(post);
post.setDetail(detail);
// thanks to CascadeType.ALL on Post.postDetail
// postDetail will be saved too
postRepository.save(post);
return "";
}
You shouldn’t be saving those 2 entities separately — you should set PostDetail inside of post object and save only the Post object. Hibernate will take care of saving the aggregated PostDetail.
That is why you are getting PersistentObjectException which you are able to workaround by keeping it inside of the same transaction.
we do not always need a bidirectional mapping when we are mapping two entities
you can simple have a unidirection most of the time
Post post = new Post();
post.setId(2L);
post.setTitle("Post 1");
PostDetail detail = new PostDetail();
detail.setMessage("Detail 1");
detail.setPost(post);
postRepository.save(post);
as you have cascade.all ,so hibernate saves Post first and then it saves PostDetail, now as per the rule of Transaction behavior ,either it is completely done or not done,Hence we can not have the situation that Post is saved but PostDetail did not,Hence to avoid such ambiguity it is important to have #Transaction annotation ,at method level or may be class level as per your requirement
Assume I have an Entity0
#Entity
public class Entity0 implements Serializable {
#Id
private Long id;
#ManyToOne
#NotNull
private Entity1 entity1;
public Entity0() {
}
public Entity0(Long id,
Entity1 entity1) {
this.id = id;
this.entity1 = entity1;
}
[getter and setter for id and entity1]
}
which references Entity1
#Entity
public class Entity1 implements Serializable {
#Id
private Long id;
#Basic
private String property0;
public Entity1() {
}
public Entity1(Long id,
String property0) {
this.id = id;
this.property0 = property0;
}
[getter and setter for id and property0]
}
The #NotNull annotation is useful to assert that Entity0.entity1 is set during persist and merge. However, it disallows to delete the reference to Entity1 (set it to null and merge the instance into the persistence context) which is necessary to delete instance of Entity1 which are referenced. I'm wondering whether there's any way to have a #ManyToOne #NotNull property in an entity at all. The following illustrates the problems which arise from the different approaches I took to make it possible to delete Entity1 instances from persistence:
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("de.richtercloud_jpa-not-nulll-many-to-one-removal_jar_1.0-SNAPSHOTPU");
EntityManager entityManager = entityManagerFactory.createEntityManager();
Entity1 entity1 = new Entity1(2l, "abc");
Entity0 entity0 = new Entity0(1l, entity1);
entityManager.getTransaction().begin();
entityManager.persist(entity1);
entityManager.persist(entity0);
entityManager.flush();
entityManager.getTransaction().commit();
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
entity1 = entityManager.merge(entity1);
entity0 = entityManager.merge(entity0);
//1: fails due to `ERROR 23503: DELETE on table 'ENTITY1' caused a violation of foreign key constraint 'FK5CQRG47R3H3KQG834IH36DUB' for key (2). The statement has been rolled back.`
//entityManager.remove(entity1);
//entityManager.flush();
//entityManager.merge(entity0);
//entityManager.flush();
//2:
entity0.setEntity1(null);
entityManager.remove(entity1);
entityManager.flush();
entityManager.merge(entity0);
entityManager.flush();
entityManager.getTransaction().commit();
entityManager.close();
entityManagerFactory.close();
The code contains so many flushs because I'm reproducing this for a JTA-environment where a flush can occur between the parts of a transaction.
I'm aware that I can drop the #NotNull annotation to work around the problem. My question is whether there's any solution to have both annotations and be able to delete. In case that's no possible, is it more common to set the reference temporarily to a bogus Entity1 instance or to give up on #NotNull?
Cascading might be a valid way, if not the way, however I've given up on it because I found it to be hiding issues and behaving in an unwanted fashion - not that it's not possible to master, it's just easier to handle for me personally.
If cascading is a way you could add the following to your entity1
#OneToMany(cascade=CascadeType.REMOVE)
private List<Entity0> entity0s;
This way the referenced entity0s will be deleted when entity1 is deleted.
Cascading can be dangerous and you should always think twice about it whether it is a good idea to always delete the referenced entities. Sometimes it is better to delete the references first and then remove the "parent" entity.
I'm not sure I see what the problem is, why do you think you have to set #NotNull on a #ManyToOne foreign key declaration? #NotNull is not a JPA annotation. If you want to make the database column set to not null you should use:
#ManyToOne
#JoinColumn(name = "entity1_id", nullable=false)
private Entity1 entity1;
So if you want to have nullable=false and delete them both then delete the child first then the parent.
tx.begin();
entity1 = em.find(Entity1.class, 1L);
entity0 = em.find(Entity0.class, 2L);
em.remove(entity0);
em.remove(entity1);
tx.commit();
In one of our spring batch jobs, we create additional entities (CompanyProfile) during processing and persist them to the DB (in a separate transaction). These entities are referenced by other entities (Vacancy), which will be persisted by the writer, but unfortunate the writer fails with this error:
Caused by: javax.persistence.EntityNotFoundException: Unable to find com.company.CompanyProfile with id 1409881
The model is as follows:
#Entity
public class Vacancy {
#ManyToOne(fetch = FetchType.EAGER, optional = true)
#JoinColumn(name = "company", nullable = true)
private CompanyProfile company;
...
}
#Entity
public class CompanyProfile {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
}
In the processor we have this:
CompanyProfile company = companyProfileService.handleCompany(compName);
vacancy.setCompany(company);
Where the method companyProfileService.handleCompany() is annotated with #Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW )
I'm sure the CompanyProfile gets persisted - I can see it in the DB, but when the Vacancy gets saved by the ItemWriter, it fails with the above exception. (also, note that the id of the persisted entity is mention in the exception above)
Do you see any reason why the writer would fail in this case?
With information you gave us my guess is that transaction opened by SB is unable to see data persisted by companyProfileService.handleCompany() method because service component uses a different transaction than SB ones; you have to check database ISOLATION_LEVEL property
I have to JPA Entities defined with a bidirectional relationship many to one, hereby:
#Entity
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="DEPARTAMENTO_ID_GENERATOR",sequenceName="DEPARTAMENTO_SEQ")
#GeneratedValue(strategy=GenerationType.SEQUENCE,generator="DEPARTAMENTO_ID_GENERATOR")
#Column(name="DEP_ID")
private long id;
#Column(name="DEP_DESC")
private String desc;
//bi-directional many-to-one association to Academico
#OneToMany(mappedBy="department")
private Set<Proffesor> proffesors;
//getters and setters
}
#Entity
#Table(name="ACADEMICOS")
public class Proffesor implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="ACADEMICOS_ID_GENERATOR", sequenceName="ACADEMICOS_SEQ")
#GeneratedValue(strategy=GenerationType.SEQUENCE,generator="ACADEMICOS_ID_GENERATOR")
#Column(name="ACD_ID")
private long id;
#ManyToOne(cascade={CascadeType.PERSIST,CascadeType.MERGE})
#JoinColumn(name="ACD_DEPADSCRITO_DEP")
private Department department;
// getters and setters.
}
After in a transactional Spring service I have the next code to manipulate the entities in this way.
#Transactional (propagation=Propagation.REQUIRED)
public void createDepartmentWithExistentProffesor(String desc,Long idAvaiableProf) {
// new department
Department dep = new Department();
dep.setDesc(desc);
HashSet<Proffesor> proffesors = new HashSet<Proffesor>();
dep.setProffesors(proffesors);
// I obtain the correct attached Proffesor entity
Proffesor proffesor=DAOQueryBasic.getProffesorById(idAvaiableProf);
// I asign the relationship beetwen proffesor and department in both directions
dep.addProffesors(proffesor);
// Persists department
DAODataBasic.insertDepartment(dep);
// The id value is not correct then Exception ORA-0221
System.out.println("SERVICIO: Departamento creado con id: " + dep.getId());
}
As I said in the comments the id of the new Department persisted is not a real database id inside the transaction, then it is produced an exception
Exception in thread "main" org.springframework.orm.jpa.JpaSystemException: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
........
Caused by: java.sql.BatchUpdateException: ORA-02291: integrity restiction (HIBERNATE_PRB.FK_ACD2DEP) violated - primary key don't found
I've tried in a test, persist the new departmen entity with no relationship with Proffesor and I've seen that the id of the new department persisted entity has not a valid value inside the transaction but out of the transaction already the id has a correct value.
But I need the correct value inside the transaction.
Can anybody help me?
Thank you in advance.
try this
#OneToMany(mappedBy="department",cascade = CascadeType.PERSIST)
private Set<Proffesor> proffesors;