I am using Spring Data JPA repositories. I have a Card entity and a Transaction entity. When user perform a transaction with card then i would like to save Card and transaction(purchase/refund) entities both. But when user performs next transaction then i want to save Transaction entity only. My Entities are :
Card Entity
#Entity
#Table(name = "CARD")
public class Card {
#Id
private Long card_id;
public Long getCard_id() {
return card_id;
}
public void setCard_id(Long card_id) {
this.card_id = card_id;
}
private String type;
}
Transaction Entity
#Entity
#Table(name="Transaction")
public class Transaction {
#Id
#SequenceGenerator( name="TRAN_SEQ1", initialValue=5,sequenceName="TRAN_SEQ1", allocationSize=1 )
#GeneratedValue( strategy=GenerationType.SEQUENCE, generator="TRAN_SEQ1")
private long id;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "card_id")
private Card card;
public Card getCard() {
return card;
}
public void setCard(Card card) {
this.card = card;
}
}
I have tried with below approach but it throws below exception on save:
Transaction t = new Transaction();
Card c = cardRepository.getOne(123L);
t.setCard(c);
transactionRepository.save(t);
**Exception :
org.hibernate.PersistentObjectException: uninitialized proxy passed to persist()**
I am not sure what I am missing. Can anyone guide me here..
Have you tried to add the reverse relationship?
#Entity
#Table(name = "CARD")
public class Card {
#Id
private Long card_id;
#OneToMany
private List<Transaction> transactions = new ArrayList<>();
// Getters and Setters
}
Related
I am trying to make a connection between an Event and User entity, so that I can save entrants into an event. I have never used #ManyToMany mapping before and so have been following a tutorial. When I try to post the data via postman (eventid and userid), I get null values for both.
So far I have, User and Event entity,
#Data
//Entity maps object to database
#Entity
#NoArgsConstructor
#Table(name = "member")
public class User implements UserDetails, Serializable {
//More fields
//Relationship between user and events to get entrants
#OneToMany(mappedBy = "userid", fetch = FetchType.LAZY)
Set<Entrants> entrants;
#Data
//Entity maps object to database
#Entity
#NoArgsConstructor
#Table(name = "event")
public class Event implements Serializable {
//More fields
//Relationship with event and users for entrants to an event
#OneToMany(mappedBy = "eventid",fetch = FetchType.LAZY)
Set<Entrants> entrants;
Then I have an Entrant Entity to hold the entrants to an event.
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Entrants implements Serializable {
#Id
#GeneratedValue
Long id;
#ManyToOne
#JoinColumn(name = "user_id")
User userid;
#ManyToOne
#JoinColumn(name = "event_id")
Event eventid;
}
Then in my controller,
#PostMapping("/management/events/entrants")
GenericResponse createEntrant(#Valid #RequestBody Entrants entrant) {
System.out.println("entrant is: " +entrant);
entrantService.save(entrant);
return new GenericResponse("Entrant saved");
}
EntrantService
public Entrants save(Entrants entrants) {
return entrantRepository.save(entrants);
}
and the repository is the standard and the above utilises the save() method.
If I post the following in Postman,
{
"user_id": 1,
"event_id": 1
}
I get this
entrant is: Entrants(id=null, userid=null, eventid=null)
id is obviously created by Spring, but the userid and eventid are null.
From my limited knowledge I think this is something to do with the 2 fields in the Entrants entity, being of type User and Event rather than int. But I am not sure how to get around this.
The tutorial I followed wasnt really based on my implementation so I have had to change quite a lot.
You could use a Dto in your controller like this:
#PostMapping("/management/events/entrants")
createEntrant(#Valid #RequestBody EntrantDto entrant) {
System.out.println("entrant is: " +entrant);
entrantService.save(entrant);
return new GenericResponse("Entrant saved");
}
EntrantDto.java
public class EntrantDto {
private Long user_id;
private Long event_id;
// no-args constructor, getter, setter,...
}
and modify a little bit your service like
public Entrants save(EntrantDto entrant) {
User user = this.userRepository.findById(entrant.getUser_id()).orElseThrown(IllegalArgumentException::new);
Event event = this.eventRepository.findById(entrant.getEvent_id()).orElseThrown(IllegalArgumentException::new);
Entrants entrants = new Entrants(user, event);
return entrantRepository.save(entrants);
}
Hy all
I'm having a hard time solving the following spring jpa problem.
Let's say I have the following simple data model (two entities with a one direction relationship between the two)
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#MappedSuperclass
public class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Long version;
}
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Entity
public class Entity1 extends AbstractEntity {
private String name;
}
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Entity
public class Entity2 extends AbstractEntity {
private String name;
#ManyToOne(cascade={ALL})
private Entity1 entity1;
}
and the following plumbing to store them
public interface Entity1Dao extends JpaRepository< Entity1, Long >, JpaSpecificationExecutor< Entity1 > {
Entity1 findByName(String name);
}
public interface Entity2Dao extends JpaRepository< Entity2, Long >, JpaSpecificationExecutor< Entity2 > {
Entity2 findByName(String name);
}
#Service
public class StoreService {
#Autowired
Entity1Dao dao1;
#Autowired
Entity2Dao dao2;
#Transactional
public Entity1 saveEntity1(Entity1 e) {
return dao1.save(e);
}
#Transactional
public Entity2 saveEntity2(Entity2 e) {
return dao2.save(e);
}
public Entity1 loadEntity1ByName(String name) {
return dao1.findByName(name);
}
}
#SpringBootApplication
public class JpaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JpaDemoApplication.class, args);
}
}
And the following test
#SpringBootTest
#TestMethodOrder(value = MethodOrderer.OrderAnnotation.class)
class JpaDemoApplicationTests {
#Autowired
StoreService store;
#Test
#Order(1)
void contextLoads() {
assertThat(store).isNotNull();
}
#Test
#Order(2)
void insertEntity1() {
store.saveEntity1(new Entity1("test entity1"));
Entity1 saved = store.loadEntity1ByName("test entity1");
assertThat(saved).isNotNull().hasNoNullFieldsOrProperties();
}
#Test
#Order(4)
void insertEntity2WithNewEntity1() {
store.saveEntity2(new Entity2("with new entity1", new Entity1("new entity1")));
}
#Test
#Order(5)
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1", store.loadEntity1ByName("test entity1")));
}
}
the last test (i.e. insertEntity2WithExistingEntity1) fails with the following exception
org.hibernate.PersistentObjectException: detached entity passed to
persist: com.example.jpaDemo.Entity1
If I change the CascadeType in Entity2 to MERGE, that test passes but the insertEntity2WithNewEntity1 fails with the following exception
org.hibernate.TransientPropertyValueException: object references an
unsaved transient instance - save the transient instance before
flushing : com.example.jpaDemo.Entity2.entity1 ->
com.example.jpaDemo.Entity1
I've tried multiple combination of cascading types bute it seems that as soon as PERSIST is used, the last test fails (and ALL includes PERSIST).
I would have expected that if MERGE and PERSIST are set, they would both be active but form the test it looks like MERGE is ignored when PERSIST is set.
Any clues, tips, hints at what I'm doing wrong so that both tests run???
EDIT
The tests are suppose to mimick the behaviour of a REST service endpoint reveiving and saving json reprensentation of an Entity1.
The json for the third test would be
{ name: "with new entity1", entity1: { name: "new entity1"}}
The json for the fourth would be
{ name: "with new entity1", entity1: { id: 1, version: 0, name: "test entity1"}}
JPA should persists the entity1 in the third test because it's id is null but should merge the one in the fourth test because it's id is not null.
I am however unable to do both, it's either one or the other.
EDIT 2
I've modified Entity1 slightly to have a reference to the list of Entity2 associated to it and annotated it with #OneToMany and the same cascading type as in Entity2 and it's the same behavior.
When I set the cascading type to MERGE and only Merge, I'm able to save a new entity that has a reference with an existing one but I can't save a new entity with a reference to a new one.
When I set the cascading type to PERSIST (i.e PERSIST on its own, PERSIST and MERGE or ALL), it's the oppposit; I can save a new entity with a reference to anther new entity but I can't save a new entity with a reference to an already existing one.
So it's seem that when PERSIST is set, it overrides the behavior of MERGE. That, to me, is a bug. Is it not?
I've uploaded the source to github in case you want to experiment or take a look at it yourself. https://github.com/willix71/persistVsMerge.git
You need to add #Transactional on your last test. The entity loaded is detached as there is no outer transaction, you can't persist it.
#Test
#Order(5)
#Transactional
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1", store.loadEntity1ByName("test entity1")));
}
I'm not sure if this is relevant anymore, but the code below works as I would expect. Removing "cascade = CascadeType.PERSIST" will fail the persist test with "object references an unsaved transient instance".
I also noticed in your github repo that you are attempting to do cascading both from parent to child and child to parent. I think this is the root cause of your issues.
Entities:
#Entity
#Table(name = "users")
#Getter
#Setter
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
UUID id;
#ManyToOne(cascade = CascadeType.PERSIST)
Address address;
}
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#OneToMany(mappedBy = "address")
List<User> user;
}
Repositories:
public interface UserRepository extends JpaRepository<User, UUID> {
}
public interface AddressRepository extends JpaRepository<Address, UUID> {
}
Tests:
#DataJpaTest
#Import(DataSourceConfig.class)
class UserRepositoryTest {
#Autowired
private UserRepository userRepository;
#Autowired
private AddressRepository addressRepository;
#Test
void testMerge() {
var address = new Address();
addressRepository.save(address);
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
#Test
void testPersist() {
var address = new Address();
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
}
I have build my data model using JPA and am using Hibernate's EntityManager to access the data. I am using this configuration for other classes and have had no problems.
The issue is that I created an entity with a composite primary key (the two keys are foreign keys) , adding elements works perfectly I checked it in database but I am not able to retrieve the populated row from database.
For example if I query "FROM Referentiel" to return a list of all referentiels in the table, I get this [{},{}] my list.size() has the proper number of elements (2), but the elements are null.
The entity:
#Entity
#Table(name = "Et_referentiel")
public class Referentiel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name = "id_projet")
private Projet projet;
#Id
#ManyToOne
#JoinColumn(name = "id_ressource")
private Ressource ressource;
#Column(name = "unite", nullable = false)
private String unite;
}
here is my controller getList method:
#PostMapping(value = "/list", consumes = { MediaType.APPLICATION_JSON_UTF8_VALUE })
public List<Referentiel> listReferentiel(#RequestBody Long idProjet) {
List<Referentiel> referentiel = referentielService.listReferentiel(idProjet);
return referentiel;
}
and here is my dao methods:
#Autowired
private EntityManager em;
#Override
public void ajouterReferentiel(Referentiel ref) {
em.persist(ref);
em.flush();
}
#SuppressWarnings("unchecked")
#Override
public List<Referentiel> listReferentiel(Long idProjet) {
Query query = em.createQuery("Select r from Referentiel r where r.projet.idProjet=:arg1");
query.setParameter("arg1", idProjet);
em.flush();
List<Referentiel> resultList = query.getResultList();
return resultList;
}
Any help is greatly appreciated.
Try creating a class representing your composite key:
public class ReferentielId implements Serializable {
private static final long serialVersionUID = 0L;
private Long projet; // Same type than idProjet, same name than inside Referentiel
private Long ressource; // Same type than idRessource (I guess), same name than inside Referentiel
// Constructors, getters, setters...
}
And assign it to your entity having that composite key.
#Entity
#IdClass(ReferentielId.class) // <- here
#Table(name = "Et_referentiel")
public class Referentiel implements Serializable {
// ...
}
Notice that it is required to have a class representing your composite keys, even if that does not help in your problem.
I am facing a very weird issue in JPA entity manager. I have tow Entities
1) Incident
2) Country
Country is master and Incident is child with ManyToOne.
Incident.java
#Entity
#Table(name = "Incident")
public class Incident {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "incidentID")
private Integer incidentID;
#Column(name = "incidentTitle")
private String incidentTitle;
#ManyToOne
#JoinColumn(name = "countryID")
private Country country;
#Transient
#ManyToOne
#JoinColumn(name = "countryID")
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
// Getter and setters
}
Country.Java
#Entity
#Table(name="Country")
public class Country {
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "country", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Incident> incident;
#OneToMany
#JoinColumn(
name="countryID",nullable=false)
public List<Incident> getIncident() {
return incident;
}
public void setIncident(List<Incident> incident) {
this.incident = incident;
}
//getter and setter
}
RepositoryImpl.java
#Repository
#Transactional
public class IncidentRepositoryImpl implements IncidentRepository{
#PersistenceContext
private EntityManager em;
#Autowired
public void setEntityManager(EntityManagerFactory sf) {
this.em = sf.createEntityManager();
}
#Override
public Incident addIncident(Incident incident) {
try {
em.getTransaction().begin();
em.persist(incident);
em.getTransaction().commit();
return incident;
} catch (HibernateException e) {
return null;
}
}
public Incident findById(int id) {
Incident incident = null;
incident = (Incident) em.find(Incident.class, id);
return incident;
}
}
When i add Incident, incident added successfully with countryID in Incident table, But when i try to fetch the same incident, country name comes null. But when i take restart of server or redeploy the application country name also comes. Hope there is cache issue with JAP entity manager. I try to use em.refresh(incident) in findById method, then country name comes successfully. But this refresh method is very expensive call.
Please suggest some alternate solution, how to update jpa cache automatically.
On your EntityManager em, add
#PersistenceContext(type = PersistenceContextType.TRANSACTION)
private EntityManager em;
With PersistenceContextType.TRANSACTION, Spring takes control of the life cycle of EntityManager
I am using Spring JPA to store a many-to-many relationship between User and Service with the table Acquisition. Since the bridge table contains additional columns I modelled it as having two many-to-one relationships. Both are bidirectional. Additionally the Acquisition entity has a one-to-one relationship with ServiceConfiguration.
There is no problem with saving or retrieving any of these entities. However when I try to delete the acquisition like this:
#Override
#Transactional
public void removeUsersServiceAcquisition(Long serviceId, User user) {
Service service = getService(serviceId);
Acquisition acquisition = findAcquisitionByServiceAndUser(service, user);
acquisitionRepository.delete(acquisition.getId());
log.info("\n retrieved acquisition {} ", acquisitionRepository.findOne(acquisition.getId()));
}
The change is not reflected in the database. The subsequent find within the above method returns null. But later in the code and in the database the record exists. There are no exceptions being thrown.
#Entity
#Table(name="ACQUISITION")
public class Acquisition implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
public Long getId() {
return id;
}
#ManyToOne
#JoinColumn(name="user_id")
public User getUser() {
return user;
}
#ManyToOne
#JoinColumn(name="service_id")
public Service getService() {
return service;
}
#OneToOne(mappedBy="acquisition")
public ServiceConfiguration getConfiguration() {
return configuration;
}
}
#Entity
#Table(name="USER")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
public Long getId() {
return id;
}
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval=true)
public Set<Acquisition> getAcquisitions() {
return acquisitions;
}
}
#Entity
#Table(name="SERVICE")
public class Service implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
public Long getId() {
return id;
}
#OneToMany(mappedBy="service", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval= true)
public Set<Acquisition> getAcquisitions() {
return acquisitions;
}
}
#Entity
#Table(name="SERVICE_CONFIGURATION")
public class ServiceConfiguration implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
public Long getId() {
return id;
}
#OneToOne
#JoinColumn(name="acquisition_id")
public Acquisition getAcquisition() {
return acquisition;
}
public void setAcquisition(Acquisition acquisition) {
this.acquisition = acquisition;
}
}
Here is what I did to get this to work.
The remove method changed to first remove the acquisition from both relationships in which it participated:
#Transactional
public void removeUsersServiceAcquisition(Long serviceId, User user) {
Service service = getService(serviceId);
Acquisition acquisition = findAcquisitionByServiceAndUser(service, user);
service.getAcquisitions().remove(acquisition);
user.getAcquisitions().remove(acquisition);
acquisitionRepository.delete(acquisition.getId());
log.info("\n retrieved acquisition {} ", acquisitionRepository.findOne(acquisition.getId()));
}
This resulted in "no Session" Hibernate exception.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.netstellar.sitesuite.serviceregistry.site.model.User.acquisitions, could not initialize proxy - no Session
Which I dealt with by adding fetch property to the User mapping. Not sure if this is the only way of addressing this exception.
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval=true)
public Set<Acquisition> getAcquisitions() {
return acquisitions;
}