Spring boot JPA decide cascading order of entities - java

I have 3 entity class defined as below:
#Entity
public class Rimorchio {
#Id
#GeneratedValue
private long idrimorchio;
#Column(unique = true)
private String targa;
// .... Getters and Setters
}
#Entity
public class Atk {
#Id
#GeneratedValue
private long idatk;
#Column(unique = true)
private String codice;
#Column(unique = true)
private String targa;
#OneToOne
private Rimorchio suggerito;
// .... Getters and Setters
}
#Entity
public class Trasportatore {
#Id
#GeneratedValue
private long idtrasportatore;
#Column(unique = true)
private String nometrasportatore;
#OneToMany(cascade = {CascadeType.PERSIST})
private List<Rimorchio> listarimorchi;
#OneToMany(cascade = {CascadeType.PERSIST})
private List<Atk> listaatk;
#OneToMany(cascade = {CascadeType.PERSIST})
private List<Autista> listaautisti;
// .... Getters and Setters
}
When I try to save in the database the Trasportatore class, i receive the following error:
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance -
save the transient instance before flushing :
com.petroli.gestionefasipetroli.entities.Atk.suggerito ->
com.petroli.gestionefasipetroli.entities.Rimorchio
So, I have to save the Rimorchio instance before the Atk instance.
I have a structure with a Repository that extends JpaRepository and a Service that simply call
repository.saveAndFlush(trasportatore);
How can I decide the order of insertion into DB to avoid this problem? Or there is another solution (possibly avoiding manually cascading operations) to achieve this goal?

Related

JPA - Map child entity class to parent when parent has two child objects

I have a Shipment Entity class where there are two ShipmentAddress, one for the delivery address and the other, the destination address:
#Entity
#Table(name = "shipment")
public class Shipment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private AppUser user;
#OneToOne(cascade = CascadeType.ALL)
private ShipmentAddress fromAddress;
#OneToOne(cascade = CascadeType.ALL)
private ShipmentAddress toAddress;
}
I have a ShipmentAddress Entity class where it looks like this:
#Entity
public class ShipmentAddress {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false)
private String street1;
private String street2;
#OneToOne(mappedBy = "fromAddress")
private Shipment shipment;
#OneToOne(mappedBy = "toAddress")
private Shipment shipment;
}
I think we can agree that it is redunant to have two properties to define a Shipment inside ShipmentAddress, because they both point to the same Shipment. In ShipmentAddress class, I want to be able to have one Shipment in the ShipmentAddress Entity. JPA does not support multiple #OneToOne annotations for the same entity class. How can I accomplish this?

Is there a way to retrieve an entity with list property but load the list with the last few entities?

Let's say I have the following entities in my application:
#Data
#Entity
public class SomeEntity {
#Id
private Long id;
#OneToMany
private List<AnotherEntity> anotherEntities = new ArrayList<>();
#Version
private Long version;
}
#Data
#Entity
public class AnotherEntity {
#Id
private Long id;
#Column
private String someField;
#Column
private Long version;
}
Question 1:
I want to load a SomeEntity with id = 1 for example, but I only want to load the anotherEntities partially, for example I only want the last 10 versions of it, what is the easiest and most straight forward way of doing it (with Hibernate/Spring Data JPA) with one request?
Question 2:
I want to update the previously mentioned object and add a new AnotherEntity to the list, but the save(T t) method of JpaRepository saves the whole object and I lose the ones that weren't loaded. How can I save the object so that the version will be updated by Spring Data (Optimistic Locking) and the SomeEntity won't lose previous data?
Update 1:
I am using Postgresql for database.
You have different options depending on your exact constraints.
You can use the #Where annotation:
#Data
#Entity
public class SomeEntity {
#Id
private Long id;
#OneToMany
#Where(clause = "version < 10")
private List<AnotherEntity> anotherEntities = new ArrayList<>();
#Version
private Long version;
}
You can use a filter:
#Data
#Entity
public class SomeEntity {
#Id
private Long id;
#OneToMany
#Filter(
name="latestVersions",
condition="version < :version"
)
private List<AnotherEntity> anotherEntities = new ArrayList<>();
}
You can enable filters before running a query with the session:
entityManager
.unwrap(Session.class)
.enableFilter("latestVersions")
.setParameter("version", 10);
List<Account> accounts = entityManager.createQuery(
"from SomeEntity se where se.id = 1", SomeEntity.class)
.getResultList();
You can map the association as bidirectional (or as a many-to-one)
#Data
#Entity
public class SomeEntity {
#Id
private Long id;
#OneToMany(mappedBy = "someEntity")
private List<AnotherEntity> anotherEntities = new ArrayList<>();
#Version
private Long version;
}
#Data
#Entity
public class AnotherEntity {
#Id
private Long id;
#Column
private String someField;
#Column
private Long version;
#ManyToOne
private SomeEntity someEntity;
}
Now you can get the list of entities using an HQL query:
from AnotherEntity ae where ae.someEntity.id = 1 and ae.version < 10
When you want to create a new AnotherEntity, you can get SomeEntity from any element in the result list, or you can use EntityManager#getReference (and avoid running the query):
AnotherEntity ae = new AnotherEntity(...);
ae.setSomeEntity(em.getReference(SomeEntity.class, 1));
em.persist(ae);
The association is lazy, so Hibernate is not going to load the whole collection (unless you need to access it).

OneToOne and OneToMany not storing Parent Id (IdClass)

I am currently working on a project with following data structure:
public class PinaColadaId implements Serializable {
private UUID id;
#Temporal(TemporalType.TIMESTAMP)
private Date time;
// Constructor + Getters and Setters
}
#Entity
#Table(name = "pina_coladas")
#IdClass(PinaColadaId.class)
public class PinaColadaEntity {
#Column(columnDefinition = "char(255)")
#Type(type="org.hibernate.type.UUIDCharType")
private #Id UUID id;
private #Id #Temporal(TemporalType.TIMESTAMP) Date time;
#OneToOne(mappedBy = "entityID", cascade=CascadeType.ALL)
private PineappleWrapper pineapple;
#OneToMany(mappedBy = "entityID", cascade=CascadeType.ALL)
private List<CoconutWrapper> coconuts;
// Constructor + Getters and Setters
}
#Entity
public class PineappleWrapper {
private #Id #GeneratedValue long id;
private String manufacturer;
private String origin;
#OneToOne
private PinaColadaEntity entityID;
// Constructor + Getters and Setters
}
#Entity
public class CoconutWrapper {
private #Id #GeneratedValue long id;
private int shipmentNumber;
private int juice;
#ManyToOne
private PinaColadaEntity entityID;
// Constructor + Getters and Setters
}
The issue is that Spring Boot together with Hibernate and JPA correctly generate all the tables in my database, however when I attempt to store PineappleWrapper or CoconutWrapper, it stores all the values of PineappleWrapper and/or CoconutWrapper except for the id and time of the parent. The columns are generated yet they store the value "null".
Any and all help is much appreciated, -AwesomeDude091
Edit: I am aware of the JoinColumn annotation and it proposed implementation in my Wrapper classes, but I do not know how to use them with my two ID variables (id and time)

JPA StackOverflow -- Many to Many and one to one mapping

I am trying to save a JPA entity which has ManytoMany Relationship (Consumer and Product table) and OnetoOne relation with ConsumerDetailstable.Below are my entities
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class)
#Entity
public class Consumer {
#Id
#GeneratedValue
private Long id;
private String firstName;
private String lastName;
#JsonManagedReference
#OnToMany(mappedBy = "consumer")
private Set<ConsumerProduct> consumerProducts;
#OneToOne
private CustomerDetails consumerDetails;
}
#Entity
public class Product {
#Id
#GeneratedValue
private Long productId;
private String productCode;
#OneToMany(mappedBy = "product")
private Set<ConsumerProduct> consumerProducts;
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class)
#Entity(the join table)
public class ConsumerProduct {
#EmbeddedId
ConsumerProductKey id;
#JsonBackReference
#ManyToOne
#MapsId("id")
#JoinColumn(name = "id")
private Consumer consumer;
#ManyToOne
#MapsId("productId")
#JoinColumn(name = "product_id")
private Product product;
}
#Embeddable (forgein keys combined as embeded id)
public class ConsumerProductKey implements Serializable {
#Column(name="id")
private Long id;
#Column(name = "product_id")
private Long productId;
}
#Enitity (one to one relation table)
public class CustomerDetails {
#Id
#GeneratedValue
private Long consumerDtlId;
#OneToOne
private Consumer consumer;
private String city;
private String state;
private String country;
}
To save the entity am have just extended JPARepository and called save method
public class ConsumerRepository<Consumer> Implements JPARepository<Consumer, Long> {
#Override
public Consumer S save(Consumer entity) {
return save(entity);
};
}
I get java.lang.StackOverFlowError at save method.
Anything wrong with my Mappings ?
Question: Since this will be save operation and since Consumer Id is yet to be generated how do I assign to below Entities
ConsumerProduct.ConsumerProductKey (how do i assign Id of consumer table once it is inserted to join table ? will JPA take care of it)
CustomerDetails (how do i assign Id of consumer table once it is inserted to join table ? will JPA take care of it)
EDIT: I have updated the entity with JsonManagedReference and JsonBackedReference but still i have am facing stackoverflow error
It is due to Consumer trying to access ConsumerProduct and ConsumerProduct trying to access consumer entity and end up with StackOverflow error.
You should use #JsonManagedReference and #JsonBackReference annotation in consumer and ConsumerProduct respectivly.

How properly annotate hibernate entities

Write some usual tests for my MVC webapp and stopped at findById() testing.
My model classes:
#Entity
public class Product {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private double purchasePrice;
private double retailPrice;
private double quantity;
#ManyToOne
#JoinColumn (name = "supplier_id")
private Supplier supplier;
#ManyToOne
#JoinColumn (name = "category_id")
private Category category;
#Entity
public class Category {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany
#Cascade(org.hibernate.annotations.CascadeType.ALL)
private List<Product> products;
#Entity
public class Supplier {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#LazyCollection(LazyCollectionOption.FALSE)
#Cascade(org.hibernate.annotations.CascadeType.ALL)
#OneToOne
private Contact contact;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany
private List<Product> products;
And my test code:
private Product productTest;
private Category categoryTest;
private Supplier supplierTest;
#Before
public void setUp() throws Exception {
categoryTest = new Category("Test category", "", null);
supplierTest = new Supplier("Test supplier", null, null);
productTest = new Product("Test product","", 10, 20, 5, supplierTest, categoryTest);
categoryService.save(categoryTest);
supplierService.save(supplierTest);
productService.save(productTest);
}
#Test
public void findById() throws Exception {
Product retrieved = productService.findById(productTest.getId());
assertEquals(productTest, retrieved);
}
Well, assertion failed, because of difference product.category.products and product.supplier.products properties, as you can see on pic:
One product have it as null, another as {PersistentBag}.
Sure I can easy hack it by writing custom equals method (which will ignore these properties), but sure it's not the best way.
So, why these fields are different?
I'm sure solution in properly annotation of entities fields.
Two pointers :
you use #LazyCollection(LazyCollectionOption.FALSE) in your relationship fields, so fields with that annotation are dynamically loaded by your ORM when you retrieve your entity while entites created in your fixture of your unit test are created outside from your ORM and you don't value these fields.
Even if you remove #LazyCollection(LazyCollectionOption.FALSE), you may have other differences if you want to do assertEquals() with a retrieved entity and a entity created by the hand. For example, with Hibernate, your lazy List will not be null but instance of PersistentList.
So, you should perform some works to perform assertions.
You may check properties individually or you may use Reflection to assert fields and ignore comparison for null fields in the expected object.
check http://www.unitils.org/tutorial-reflectionassert.html, it may help you.

Categories