Many to Many Hibernate mapping with extra column - java

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>();

Related

Hibernate saves additional row instead of updateing and needs two saves

Note: for simplyfication i have changed some variables names and get rid of unnecessary code to show my issue.
I have two repositories:
#Repository
public interface CFolderRepository extends CrudRepository<CFolder, Long>, QuerydslPredicateExecutor<CFolder> {}
#Repository
public interface CRepository extends JpaRepository<C, Long>, CFinder, QuerydslPredicateExecutor<C> {}
The class C is:
#FilterDef(name = "INS_COMPANY_FILTER", parameters = {#ParamDef(name = "insCompanies", type = "string")})
#Filter(name = "INS_COMPANY_FILTER", condition = " INS_COMPANY in (:insCompanies) ")
#NoArgsConstructor
#AllArgsConstructor
#Audited
#AuditOverrides({#AuditOverride(forClass = EntityLog.class),
#AuditOverride(forClass = MultitenantEntityBase.class)})
#Entity
#Table(name = "INS_C")
#Getter
public class C extends MultitenantEntityBase {
#OneToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "C_FOLDER_ID")
private CFolder cFolder;
public void addFolder(List<String> clsUrl){
this.cFolder = CFolder.createFolder(clsUrl);
}
}
CFolder is:
#Getter
#NoArgsConstructor
#Audited
#AuditOverride(forClass = EntityLog.class)
#Entity
#Table(name = "C_FOLDER")
#AllArgsConstructor
public class CFolder extends EntityBase {
#Column(name = "CREATION_FOLDER_DATE_TIME", nullable = false)
private LocalDateTime creationFolderDateTime;
#Column(name = "UPDATED_FOLDER_DATE_TIME")
private LocalDateTime updatedFolderDateTime;
#Column(name = "FOLDER_CREATED_BY", nullable = false)
private String folderCreatedBy;
#Column(name = "FOLDER_UPDATED_BY")
private String folderUpdatedBy;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "cFolder", fetch = FetchType.EAGER)
#NotAudited
private Set<FolderDocument> folderDocuments = new HashSet<>();
public static CFolder createFolder(List<String> clsUrl){
CFolder cFolder = new CFolder(LocalDateTime.now(), null, SecurityHelper.getUsernameOfAuthenticatedUser(), null, new HashSet<>());
createFolderDocuments(clsUrl, cFolder);
return cFolder;
}
public void updateFolder(List<String> clsUrl){
this.updatedFolderDateTime = LocalDateTime.now();
this.folderUpdatedBy = SecurityHelper.getUsernameOfAuthenticatedUser();
this.folderDocuments.clear();
createFolderDocuments(clsUrl, this);
}
private static void createFolderDocuments(List<String> clsUrl, CFolder cFolder) {
int documentNumber = 0;
for (String url : clsUrl) {
documentNumber++;
cFolder.folderDocuments.add(new FolderDocument(cFolder, documentNumber, url));
}
}
}
FolderDocument is:
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Audited
#AuditOverride(forClass = EntityLog.class)
#Entity
#Table(name = "FOLDER_DOCUMENT")
public class FolderDocument extends EntityBase {
#ManyToOne
#JoinColumn(name = "C_FOLDER_ID", nullable = false)
private CFolder cFolder;
#Column(name = "DOCUMENT_NUMBER", nullable = false)
private int documentNumber;
#Column(name = "URL", nullable = false)
private String url;
}
And finally we have a service in which i use these entities and try to save/load them to/from database:
#Service
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class CFolderService {
private final CRepository cRepository;
private final CommunicationClServiceClient communicationServiceClient;
private final CFolderRepository cFolderRepository;
public List<ClDocumentDto> getClCaseFolder(Long cId) {
C insCase = cRepository.findCById(cId);
List<ClDocumentDto> clDocumentsDto = getClDocuments(insCase.getCNumber()); // here, the object has one cFolder, but many FolderDocument inside of it
return clDocumentsDto;
}
#Transactional
public void updateCFolder(Long cId) {
C insC = cRepository.findCById(cId);
List<ClDocumentDto> clDocumentsDto = getClDocuments(insC.getCNumber());
List<String> clsUrl = clDocumentsDto.stream().filter(c -> "ACTIVE".equals(c.getCommunicationStatus())).map(ClDocumentDto::getUrl).collect(Collectors.toList());
if (Objects.isNull(insC.getCFolder())) {
insC.addFolder(clsUrl);
} else {
insC.getCFolder().updateFolder(clsUrl);
}
cFolderRepository.save(insC.getCFolder()); // here it saves additional FolderDocument instead of updateing it
cRepository.save(insC); // need second save, so can get these collection in getClaimCaseFolder successfully
}
}
I have two issues inside. In the example i was trying to clear the objects that i found from DataBase and create new ones.
1)
First is that i have to make two save operation to successfully restore the object in getClCaseFolder method (outside transactional).
2)
Second is that everytime i am saving - i get additional FolderDocument object pinned to CFolder object inside C object. I want to clear this collection and save new one.
I am not sure why hibernate does not update this object?
EDIT:
I think that i do sth like:
cRepository.save(insC);
instead of this.folderDocuments.clear();
i can do:
for(Iterator<FolderDocument> featureIterator = this.folderDocuments.iterator();
featureIterator.hasNext(); ) {
FolderDocument feature = featureIterator .next();
feature.setCFolder(null);
featureIterator.remove();
}
But i get eager fetching, why lazy wont work? There is an error using it.
Check whether you are setting ID in that Entity or not.
If ID is present/set in entity and that ID is also present in DB table then hibernate will update that record, But if ID is not present/set in Entity object the Hibernate always treat that object as a new record and add new record to the table instead of Updating.

hibernate update one to many items set

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

Remote Access To Jpa Entity

I'm currently working on system that consists of Java Web app and C# client app. Web app has Java Web Service, which has method that returns entity object of Program class:
#WebMethod(operationName = "getProgram")
public Program getProgram(#WebParam(name = "macAddress") String macAddress){
Device device = DeviceManager.getInstance().getDevice(macAddress);
if(device != null){
return device.getProgram();
}
return null;
}
This return object of type Program which has many properties and relations:
#Entity
#Table(name = "PROGRAM", schema = "APP")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Program.getProgramsByWeather", query = "SELECT p FROM Program p WHERE p.weather = :weather")})
public class Program extends DbEntity implements Serializable {
private static final long serialVersionUID = 1L;
#JoinColumn(name = "LOGO_ID", referencedColumnName = "ID")
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch= FetchType.EAGER)
private Logo logo;
#JoinColumn(name = "WEATHER_ID", referencedColumnName = "ID")
#ManyToOne
private Weather weather;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "program", orphanRemoval = true)
private List<ProgramPlaylist> programPlaylistList = new ArrayList<>();
#OneToMany(cascade = CascadeType.ALL, mappedBy = "program", orphanRemoval = true)
private List<ProgramTicker> programTickerList = new ArrayList<>();
#Column(name = "UPDATED")
private boolean updated;
public Program() {
}
public Program(String name, AppUser owner) {
super(name, owner);
}
public Logo getLogo() {
return logo;
}
public void setLogo(Logo logo) {
this.logo = logo;
}
public Weather getWeather() {
return weather;
}
public void setWeather(Weather weather) {
this.weather = weather;
}
public boolean isUpdated() {
return updated;
}
public void setUpdated(boolean updated) {
this.updated = updated;
}
#XmlElement
public List<ProgramPlaylist> getProgramPlaylistList() {
return programPlaylistList;
}
#XmlElement
public List<ProgramTicker> getProgramTickerList() {
return programTickerList;
}
#Override
public String toString() {
return "Program[ id=" + getId() + " ]";
}
}
Client can get this object and accessing some properties in client app like program.name, which it inherits from DbEntity, but when i try to call something like this:
program.logo.name
client throws NullReferenceException.
Same exception occurs when i try to iterate over the elements of programPlaylistList ArrayList.
I'm assuming that the object itself that is passed through to client isn't fully loaded.
How can i solve this problem, please help?!
EDIT
Ok, so I printed out XML response that client get from service and its populated correctly, but for some reason object fields aren't populated and are mostly null.
Why is this occurring?
Bye default, the fetch strategy for #OneToMany annotations is LAZY, have you tried specifying it to EAGER like in the #oneToOne field (fetch= FetchType.EAGER)?

how to handle Set of not yet persisted entitys? Java JPA

i have two tables mapped by JPA with One to Many relationship. I want to add Set to the Blog entity, but since BlogNodes entry did not persisted yet, they havent Id field so i have nulpointer exception when i try to add second element to Collection. I've tried to use GenerationType.TABLE for id generator, but it doesn't help. Id is still null. Here are my entity classes with some fields ometted.
The Blog.java
#Entity
#Table(name = "t_blog")
public class Blog extends VersionedEntity{
(Identified id generation)
private static final Logger log = LoggerFactory.getLogger(Blog.class);
//#ToDo: pass actual value to serialVersionUID
//private static final long serialVersionUID = 1882566243377237583L;
...
#OneToMany(mappedBy = "parentBlog", fetch = FetchType.LAZY, orphanRemoval=true, cascade = { CascadeType.PERSIST, CascadeType.MERGE})
private Set<BlogNode> blogNodes;
The BlogNode.java
#Entity
#Table(name = "t_blog_node")
public class BlogNode{
/***************************************************************************************/
#TableGenerator(name="tab", initialValue=0, allocationSize=5)
#GeneratedValue(strategy=GenerationType.TABLE, generator="tab")
#Column(name = "id", nullable = false, updatable = false)
#Id
private Long id;
public Long getId() {
return id;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BlogNode)) return false;
BlogNode that = (BlogNode) o;
return that.id.equals(id);
}
#Override
public int hashCode() {
return id == null ? 0 : id.hashCode();
}
/*************************************************************************************/
#OneToOne
#JoinColumn(name="parent_blog_fk", referencedColumnName="id", nullable = true)
private Blog parentBlog;
Main class
public List<Blog> createBlog(int n){
params.put("BlogName","SampleBlogName");
params.put("BlogAlias","defaultAlias");
params.put("BlogDescription","defaultBlog description");
List<Blog> newBlogs = new ArrayList<Blog>();
while(n-->0){
Blog entry = new Blog();
entry.setBlogName(params.get("BlogName")+n);
entry.setBlogAlias(params.get("BlogAlias")+n);
entry.setBlogDescription(params.get("BlogDescription")+n);
entry = blogDAO.save(entry);
entry.setBlogNodes(createBlogNodes(entry, NUM_OF_NODES));
entry = blogDAO.save(entry);
newBlogs.add(entry);
}
return newBlogs;
}
private Set<BlogNode> createBlogNodes(Blog blog, int numOfNodes) {
params.put("nodeTitle","SamplenodeName");
params.put("nodeAlias","defaultAlias");
params.put("nodeTeaser","default node teaser");
params.put("nodeText","default node text");
Set<BlogNode> nodes = new HashSet<BlogNode>();;
while (numOfNodes-->0){
BlogNode node = new BlogNode();
node.setNodeTitle(params.get("nodeTitle")+numOfNodes);
node.setNodeAlias(params.get("nodeAlias")+numOfNodes);
node.setNodeText(params.get("nodeText")+numOfNodes);
node.setParentBlog(blog);
node.setNodeTeaser(params.get("nodeTeaser")+numOfNodes);
//Exception raises on the second iteration
nodes.add(node);
}
return nodes;
}
Can i beat this the other way, than persist single entitys of BlogNode separately?
You are adding the Node to a plain HashSet. The only way this causes an NPE is if it's coming from the hashCode or equals methods. Again, I'll point you to the Hibernate manual on that subject. In short, those methods should not use the persistent ID for just this reason (among others).

Is it considered a best practice to synchronize redundant column properties with associations in JPA 1.0 #IdClass implementations?

Consider the following table:
CREATE TABLE Participations
(
roster_id INTEGER NOT NULL,
round_id INTEGER NOT NULL,
ordinal_nbr SMALLINT NOT NULL ,
was_withdrawn BOOLEAN NOT NULL,
PRIMARY KEY (roster_id, round_id, ordinal_nbr),
CONSTRAINT participations_rosters_fk FOREIGN KEY (roster_id) REFERENCES Rosters (id),
CONSTRAINT participations_groups_fk FOREIGN KEY (round_id, ordinal_nbr) REFERENCES Groups (round_id , ordinal_nbr)
)
Here the JPA 1.0 #IdClass entity class:
#Entity
#Table(name = "Participations")
#IdClass(value = ParticipationId.class)
public class Participation implements Serializable
{
#Id
#Column(name = "roster_id", insertable = false, updatable = false)
private Integer rosterId;
#Id
#Column(name = "round_id", insertable = false, updatable = false)
private Integer roundId;
#Id
#Column(name = "ordinal_nbr", insertable = false, updatable = false)
private Integer ordinalNbr;
#Column(name = "was_withdrawn")
private Boolean wasWithdrawn;
#ManyToOne
#JoinColumn(name = "roster_id", referencedColumnName = "id")
private Roster roster = null;
#ManyToOne
#JoinColumns(value = {#JoinColumn(name = "round_id", referencedColumnName = "round_id"), #JoinColumn(name = "ordinal_nbr", referencedColumnName = "ordinal_nbr")})
private Group group = null;
public Participation()
{
}
public Integer getRosterId()
{
return rosterId;
}
public void setRosterId(Integer rosterId)
{
this.rosterId = rosterId;
}
public Integer getRoundId()
{
return roundId;
}
public void setRoundId(Integer roundId)
{
this.roundId = roundId;
}
public Integer getOrdinalNbr()
{
return ordinalNbr;
}
public void setOrdinalNbr(Integer ordinalNbr)
{
this.ordinalNbr = ordinalNbr;
}
public Boolean getWasWithdrawn()
{
return wasWithdrawn;
}
public void setWasWithdrawn(Boolean wasWithdrawn)
{
this.wasWithdrawn = wasWithdrawn;
}
public Roster getRoster()
{
return roster;
}
// ???
public void setRoster(Roster roster)
{
this.roster = roster;
}
public Group getGroup()
{
return group;
}
// ???
public void setGroup(Group group)
{
this.group = group;
}
...
}
In general, should the association setters synchronize with the redundant fields, here rosterId, roundId, and ordinalNbr?:
// ???
public void setGroup(Group group)
{
this.group = group;
this.roundId = group.getRoundId();
this.ordinalNbr = group.getOrdinalNbr();
}
Thanks
Yes, they should be kept in synch. Although because they are part of the Id you should never be changing these, so it is really only an issue for new objects.
If you do not keep them in synch, then for a new object they will be null/0, which is probably not good. There is no magic in JPA that will keep these in synch for you.
If you read the object from the database, then they will be in synch of coarse, but you are responsible for maintaining your object's state once in memory, including both duplicate fields, and bi-directional mappings.
If you are using JPA 2.0, why bother having the duplicate Ids at all. You can remove the routersId and the roundId and just add the #Id to the #ManyToOnes.

Categories

Resources