hibernate update one to many items set - java

Sales Order Entity.
#Entity
#Table(name = "sales_orders")
#IdClass(ReceiptPK.class)
public class SalesOrders implements Serializable {
public SalesOrders() {
}
#Id
protected Integer receiptID;
#Id
protected Integer dateKey;
public SalesOrders(Integer receiptID, Integer dateKey) {
this.receiptID = receiptID;
this.dateKey = dateKey;
}
//order contains many details
#OneToMany(fetch = FetchType.LAZY, mappedBy = "salesOrders")
#Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
private Set<SalesOrderDetails> orderDetails = new HashSet<SalesOrderDetails>();
public Set<SalesOrderDetails> getOrderDetails() {
return orderDetails;
}
public void setOrderDetails(Set<SalesOrderDetails> orderDetails) {
this.orderDetails = orderDetails;
}
// other property ..
Order Details Entity.
#Entity
#Table(name = "sales_order_details")
public class SalesOrderDetails implements Serializable {
public SalesOrderDetails() {
}
private int id;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
// Order holder
private SalesOrders salesOrders;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "order_num", referencedColumnName = "receiptID"),
#JoinColumn(name = "date_key", referencedColumnName = "dateKey")})
public SalesOrders getSalesOrders() {
return salesOrders;
}
public void setSalesOrders(SalesOrders salesOrders) {
this.salesOrders = salesOrders;
}
// other property ...
My Question : When i try to remove Order item that doesn't affect on sales_order_details .
SalesOrders saleOrder = (SalesOrders) getSession().get(SalesOrders.class ,new ReceiptPK(receiptID,dateKey));
saleOrder.getOrderDetails().remove(someDetails);
getSession().beginTransaction();
getSession().saveOrUpdate(saleOrder);
getSession().getTransaction().commit();
But someDetails doesn't removed.
-- Any help will be appreciated ...

I think that you have to commit the same transaction.
getSession().getTransaction().begin();
getSession().saveOrUpdate(saleOrder);
getSession().getTransaction().commit();

For bi-directional associations you should(must) always ensure that the associations are set correctly.
Do not allow direct access to your collections.
Provide add and remove methods for modification.
public Set<SalesOrderDetails> getOrderDetails() {
//force to use add/remove to ensure consistent object model
return Collections.unmodifiableSet(orderDetails);
}
public void addOrderDetails(SalesOrderDetails salesOrderDetails){
orderDetails.add(salesOrderDetails);
salesOrderDetails.setSalesOrders(this); //important
}
public void removeOrderDetails(SalesOrderDetails salesOrderDetails){
orderDetails.remove(salesOrderDetails);
salesOrderDetails.setSalesOrder(null); //important
}
Additionally, have you implemented equals() and hashCode() on your Entitites i.e. when you call salesOrder.getOrderDetails().remove(someDetails) is anything actually being removed from the collection?
Probably not if you have not implemented equals() and hashCode() on SalesOrderDetails.
Fianlly, you shout set the orphanRemoval flag on the OneToMany mapping to true:
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Orphan_Removal_.28JPA_2.0.29

Related

Spring Data JPA Many to Many Service Repository Problem

I am trying to add ManyToMany entity to my application. I created entity but cannot implement it.
Actor class
#Entity
#Table(name = "actor")
public class Actor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false, name = "actor_name")
private String actorName;
#ManyToMany(mappedBy = "actor", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Movie> movie = new HashSet<Movie>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getActorName() { return actorName; }
public void setActorName(String actorName) {
this.actorName = actorName;
}
public Set<Movie> getMovie() {
return movie;
}
public void setMovie(Set<Movie> movie) {
this.movie = movie;
}
}
In movie class I have
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "movie_actor",
joinColumns = {#JoinColumn(name = "movie_id")},
inverseJoinColumns = {#JoinColumn(name = "actor_id")}
)
Set<Actor> actor = new HashSet<Actor>();
........................
public Set<Actor> getActor () {
return actor;
}
public void setActor(Set<Actor> actor){
this.actor = actor;
}
I created my entity just like this but in MovieService;
Actor actor = ActorRepository.findByActorName(movie.getActor().getActorName());
movie.setActor(actor);
This part gives me error. movie.getActor().getActorName() method cannot find. Where do I need to look? In IDE it also says method getActorName and setActorName is never used. I am also adding my ActorRepository and ActorService to closer look to the problem.
ActorRepository
public interface ActorRepository extends JpaRepository<Actor, Integer> {
Set<Actor> findByActorName(String actorName);
}
ActorService
#Service
public class ActorService {
private ActorRepository actorRepository;
#Autowired
public ActorService(ActorRepository actorRepository) {
this.actorRepository = actorRepository;
}
public List<Actor> getAllActor() {
return actorRepository.findAll();
}
}
After adding ManyToMany I was using is as OneToMany entity. Services is works for OneToMany. How can I use them for ManyToMany? I need to add multiple actors to my movies. I couldn't find MVC projects for ManyToMany implementation.
You're invoking movie.getActor().getActorName() which basically does a getActorName() on a Set<Actor> object.
You're basically treating the relation as a ManyToOne instead of a OneToMany
You could use the following to fetch the first Actor of the Set
ActorRepository.findByActorName(movie.getActors().iterator().next().getActorName());
But then of course, you don't have all your Actor's names
What you could do is the following
public interface ActorRepository extends JpaRepository<Actor, Integer> {
Set<Actor> findByActorNameIn(List<String> actorName);
}
And invoke it that way
ActorRepository.findByActorNameIn(
movie.getActors()
.stream()
.map(Actor::getName)
.collect(Collectors.toList())
);

Transient field is lost when appending new OneToMany entity

I have two entities which are linked via a OneToMany relationship:
#Entity
#Table(name="bookcase")
public class BookCase {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Transient
#Getter #Setter private Long oldId;
/*
https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/
*/
#OneToMany(mappedBy = "bookCase", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Bookshelf> bookShelves = new HashSet<>();
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Set<Bookshelf> getBookShelves() { return bookShelves; }
public void setBookShelves(Set<Bookshelf> bookShelves) { this.bookShelves = bookShelves; }
}
#Entity
#Table(name="bookshelf")
public class Bookshelf {
private static final Logger log = LoggerFactory.getLogger(Bookshelf.class);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Transient
#Getter #Setter private Long oldId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bookcase_id")
private BookCase bookCase;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public BookCase getBookCase() { return bookCase; }
public void setBookCase(BookCase bookCase) {
this.bookCase = bookCase;
bookCase.getBookShelves().add(this);
}
#Transient
#Setter private OldIdListener oldIdListener;
/*
When the id is saved, listening DTOs can update their ids
*/
#PostPersist
public void triggerOldId() {
log.info("Postpersist triggered for {}", id);
if (oldIdListener != null) {
oldIdListener.updateId(oldId, id);
}
}
}
public interface OldIdListener {
void updateId(long oldId, long newId);
}
The following test fails:
#Test
public void testThatCascadingListenerIsTriggered() {
var mock = mock(OldIdListener.class);
var mock2 = mock(OldIdListener.class);
var mock3 = mock(OldIdListener.class);
var bookcase = new BookCase();
var shelf1 = new Bookshelf();
shelf1.setOldId(-5L);
shelf1.setBookCase(bookcase);
shelf1.setOldIdListener(mock);
var shelf2 = new Bookshelf();
shelf2.setOldId(-6L);
shelf2.setBookCase(bookcase);
shelf2.setOldIdListener(mock2);
var saved = bookCaseRepository.save(bookcase);
verify(mock).updateId(eq(-5L), anyLong());
verify(mock2).updateId(eq(-6L), anyLong());
var savedBookCase = bookCaseRepository.findById(saved.getId()).get();
assertThat(savedBookCase.getBookShelves()).hasSize(2);
var shelf3 = new Bookshelf();
shelf3.setOldId(-10L);
shelf3.setBookCase(savedBookCase);
shelf3.setOldIdListener(mock3);
savedBookCase.getBookShelves().add(shelf3);
bookCaseRepository.save(savedBookCase);
verify(mock3).updateId(eq(-10L), anyLong());
}
mock3 is never called.
When debugging the code, I can see that the transient fields oldId and oldIdListener are set to null when the #PostPersist method is called on object shelf3, not on shelf1 and 2.
I think this is because I am modifying the Set object; but the object is correctly persisted, it just loses all transient fields. This does not happen when the entire tree is persisted for the first time.
Is this the wrong way to insert a new element to a OneToMany set or where is the error here?
I'm using Spring Boot 2.1.
Thanks!
The field which annotation with #Transient will not persist to the database, so if you want it to persist, you must remove #Transient.

Many to Many Hibernate mapping with extra column

I've seen this question on here a few times, however none of the answers fix my issue.
I'm trying to deconstruct a many-to-many relationship down to seperate many-to-one and one-to-many entities so I can add additional columns. From what I have, the main entity saves to the database, but the intermediate does not. If anyone can figure out what's going on I would very much appreciate it. I tried doing this the other way with the primary key composite (aka: #AssociationOverride) but it also did not work. I've scowered the web but cannot find an answer to my issue here.
This is my main entity, MaintOrder:
#Entity
#Table(name="maint_orders")
public class MaintOrder extends PersistedObject implements java.io.Serializable {
...
#OneToMany(cascade = CascadeType.ALL, mappedBy="maintOrder")
private Set<ManPowerLine> manPower = new HashSet<ManPowerLine>() ;
public void addManPower(ManPower manPower, Integer quantity, Float price) {
ManPowerLine mpLine = new ManPowerLine();
mpLine.setManPower(manPower);
mpLine.setMaintOrder(this);
mpLine.setManPowerID(manPower.getManPowerID());
mpLine.setMaintOrderID(this.getMaintOrderID());
mpLine.setQuantity(quantity);
mpLine.setPrice(price);
this.manPower.add(mpLine);
// Also add the association object to the employee.
manPower.getMaintOrder().add(mpLine);
}
... getters and setters
}
Here is my secondary entity, ManPower:
#Entity
#Table(name="man_power")
public class ManPower extends PersistedObject implements java.io.Serializable {
...id's, etc
#OneToMany(mappedBy="manPower", fetch = FetchType.EAGER)
private Set<ManPowerLine> maintOrder = new HashSet<ManPowerLine>();
public Set<ManPowerLine> getMaintOrder(){
return maintOrder;
}
public void setMaintOrder(Set<ManPowerLine> maintOrder){
this.maintOrder = maintOrder;
}
... other getters and setters
}
Here is my intermediate entity, ManPowerLine:
#Entity
#Table(name = "man_power_line")
#IdClass(ManPowerLineID.class)
public class ManPowerLine extends PersistedObject implements java.io.Serializable {
#Id
private Long maintOrderID;
#Id
private Long manPowerID;
#Column(name="quantity")
private Integer quantity;
#Column(name="price")
private Float price;
#ManyToOne
#JoinColumn(name = "maintOrderID", updatable = false, insertable = false, referencedColumnName = "maint_order_id")
private MaintOrder maintOrder;
#ManyToOne
#JoinColumn(name = "manPowerID", updatable = false, insertable = false, referencedColumnName = "man_power_id")
private ManPower manPower;
... other getters and setters
}
And my ID entity, ManPowerLineID:
public class ManPowerLineID implements java.io.Serializable {
private Long maintOrderID;
private Long manPowerID;
public Long getMaintOrderID(){
return maintOrderID;
}
public Long getManPowerID(){
return manPowerID;
}
public void setMaintOrderID(Long maintOrderID){
this.maintOrderID = maintOrderID;
}
public void setManPowerID(Long manPowerID){
this.manPowerID = manPowerID;
}
#Override
public int hashCode(){
return (int)(maintOrderID + manPowerID);
}
#Override
public boolean equals(Object obj) {
if( obj instanceof ManPowerLine){
ManPowerLineID otherID = (ManPowerLineID)obj;
boolean hey = (otherID.maintOrderID == this.maintOrderID) && (otherID.manPowerID == this.manPowerID);
return hey;
}
return false;
}
}
Finally the code which utilizes this is as follows:
private void insertObject( ) {
ServiceLocator locator = new ServiceLocator();
SessionFactory sf = locator.getHibernateSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction();
MaintOrder m = new MaintOrder();
... various setters to m
Set manPowerSet = new HashSet();
for(int i = 0; i < manPowerSet.size(); i++){
ManPower mp = new ManPower();
mp = (ManPower) manPowerSet.iterator().next();
m.addManPower(mp, quantity, cost);
}
sess.saveOrUpdate(m);
tx.commit();
sess.close();
}
Is it possible that I need to use more then just m.addManPower to add to the line? I've tried adding m.setManPowerLine, but it does not change the result.
Anyways I know its a lot of code to look at, but thanks in advance.
Turns out I fixed my own issue on this one. The problem was that I didn't set cascade = CascadeType.ALL, in ALL the right places. Specifically Here:
#OneToMany(mappedBy="manPower", fetch = FetchType.EAGER)
private List<ManPowerLine> maintOrder = new ArrayList<ManPowerLine>();
Should be:
#OneToMany(mappedBy="manPower", cascade = CascadeType.All)
private List<ManPowerLine> maintOrder = new ArrayList<ManPowerLine>();

#Formula not working in hibernate with object

I have a enum of few status value
NEW, REVIEWD, PUBLISHED, PENDING, UPDATED, SPAM, DUPLICATE, IRRELEVANT, UNPUBLISHED
I don't want to use them as enumerated so created one entity for that. For convenient I want to keep a column in entity to initialize status from enum and convert that enumerated value to a Object of status entity. for this..
I have two entity. I want to refer a column with value from another entity.
Basically I want to initialize a object with formula.
Entities are
#Entity
#Table(name = "event_status")
public class EventStatus {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="eventStatusId")
private Integer eventStatusId;
#Enumerated(EnumType.STRING)
#Column(unique = true,name="eventStatusType")
private EventStatusType eventStatusType;
public EventStatus() {
this(EventStatusType.NEW);
}
public EventStatus(EventStatusType eventStatusType) {
super();
this.eventStatusType = eventStatusType;
}
public Integer getEventStatusId() {
return eventStatusId;
}
public EventStatusType getEventStatusType() {
return eventStatusType;
}
public void setEventStatusId(Integer eventStatusId) {
this.eventStatusId = eventStatusId;
}
public void setEventStatusType(EventStatusType eventStatusType) {
this.eventStatusType = eventStatusType;
}
}
I have another entity in which I am referring object of this entity
#Entity
#Table(name = "event_")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Event implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id_")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Transient
public EventStatusType eventStatusType = EventStatusType.NEW;
#ManyToOne(fetch = FetchType.EAGER, targetEntity = EventStatus.class)
#Formula("select * from event_status where eventStatusId= 1")
private EventStatus status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public EventStatus getStatus() {
System.out.println("Event.getStatus() " + status);
return status;
}
public void setStatus(EventStatus status) {
System.out.println("Event.setStatus()");
this.status = status;
}
}
This is not giving any exception but not initializing this value.
Is it possible to initialize this EntityStatus with value of eventStatusType in Event entity
I would like to explain that based on the documentation:
5.1.4.1.5. Formula
Sometimes, you want the Database to do some computation for you rather than in the JVM, you might also create some kind of virtual column. You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read only (its value is calculated by your formula fragment).
#Formula("obj_length * obj_height * obj_width")
public long getObjectVolume()
The SQL fragment can be as complex as you want and even include subselects.
...
5.1.7.1. Using a foreign key or an association table
...
Note
You can use a SQL fragment to simulate a physical join column using the #JoinColumnOrFormula / #JoinColumnOrformulas annotations (just like you can use a SQL fragment to simulate a property column via the #Formula annotation).
#Entity
public class Ticket implements Serializable {
#ManyToOne
#JoinColumnOrFormula(formula="(firstname + ' ' + lastname)")
public Person getOwner() {
return person;
}
...
}
Also, we should use insertable = false, updatable = false, because such mapping is not editable

EJB3 - handling non-standard link tables

I have a situation where I am working with EJB3 and a legacy database. I have a situation where there is a many-to-many relationship between two tables A and B, defined through a third (link) table L.
The complication is that the link table has other fields in it other than the PK's of tables A and B. The columns are standard timestamp and user columns to record who generated the link. These two additional columns are preventing me from defining the many-many relationship using a join table annotation, as they are not nillable and so must be populated.
Does anyone know of a way around this limitation? I could define One-to-many relationships from the link table to each of the other tables in the relationship, but this is not very elegant.
Thanks,
Yes, it is but you need to make it elegant. The following super-class can be used to define arbitrary many-to-many relationship as an entity:
#MappedSuperclass
public abstract class ModelBaseRelationship {
#Embeddable
public static class Id implements Serializable {
public Long entityId1;
public Long entityId2;
#Column(name = "ENTITY1_ID")
public Long getEntityId1() {
return entityId1;
}
#Column(name = "ENTITY2_ID")
public Long getEntityId2() {
return entityId2;
}
public Id() {
}
public Id(Long entityId1, Long entityId2) {
this.entityId1 = entityId1;
this.entityId2 = entityId2;
}
#Override
public boolean equals(Object other) {
if (other == null)
return false;
if (this == other)
return true;
if (!(other instanceof Id))
return false;
final Id that = (Id) other;
return new EqualsBuilder().append(this.entityId1, that.getEntityId1()).append(this.entityId1, that.getEntityId2()).isEquals();
}
#Override
public int hashCode() {
return new HashCodeBuilder(11, 111).append(this.entityId1).append(this.entityId2).toHashCode();
}
protected void setEntityId1(Long theEntityId1) {
entityId1 = theEntityId1;
}
protected void setEntityId2(Long theEntityId2) {
entityId2 = theEntityId2;
}
}
protected Id id = new Id();
public ModelBaseRelationship() {
super();
}
public ModelBaseRelationship(ModelBaseEntity entity1, ModelBaseEntity entity2) {
this();
this.id.entityId1 = entity1.getId();
this.id.entityId2 = entity2.getId();
setVersion(0);
}
#EmbeddedId
public Id getId() {
return id;
}
protected void setId(Id theId) {
id = theId;
}
}
The example of entity based on this super class (fragment):
#Entity(name = "myRealEntity")
#Table(name = "REAL_TABLE_NAME", uniqueConstraints = { #UniqueConstraint(columnNames = {
"FIRST_FK_ID", "SECOND_FK_ID" }) })
#AttributeOverrides( {
#AttributeOverride(name = "entityId1", column = #Column(name = "FIRST_FK_ID")),
#AttributeOverride(name = "entityId2", column = #Column(name = "SECOND_FK_ID"))
})
public class ModelBaseRelationshipReferenceImpl extends ModelBaseRelationship {
private Entity1OfManyToManyRelationship entity1;
private Entity2OfManyToManyRelationship entity2;
...
#ManyToOne
#JoinColumn(name = "FIRST_FK_ID", insertable = false, updatable = false)
public Entity1OfManyToManyRelationship getEntity1OfManyToManyRelationship() {
return entity1;
}
#ManyToOne
#JoinColumn(name = "SECOND_FK_ID", insertable = false, updatable = false)
public Entity2OfManyToManyRelationship getEntity2OfManyToManyRelationship () {
return entity2;
}
...
}

Categories

Resources