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;
}
...
}
Related
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())
);
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>();
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
I want to join two tables on column "vendor",
In invoice table vendor type is integer, in vendor table, vendor is type varchar(10).
Is it possible to do a type conversion and also have a relationship?
#Entity
public class Vendor
{
private String id;
#Id(Column="vendor")
public String getId(){ ... }
}
#Entity
public class Invoice
{
private Vendor vendor;
#One-to-one
public Vendor getVendor() { ... }
}
As far as I know, using a pivot table (as you would do to represent a many-to-many relationship) would be the right way to do that.
Something like this should work:
#Entity
public class Invoice
{
#JoinTable(name = "invoice_vendor", joinColumns = {
#JoinColumn(name = "invoice", referencedColumnName = "vendor_id")}, inverseJoinColumns = {
#JoinColumn(name = "vendor", referencedColumnName = "id")})
#OneToOne
private Vendor vendor;
}
Where the invoice_vendor table has the integer id in id column and varchar reference in vendor_id column.
Also I surmise you would like a ManyToOne relationship between vendors, but you've written one to one, so I have left that as such.
Maybe this can be done using a transient field
#Entity
public class Employee {
...
private boolean isActive;
...
#Transient
public boolean getIsActive() {
return isActive;
}
public void setIsActive(boolean isActive) {
this.isActive = isActive;
}
#Basic
private String getIsActiveValue() {
if (isActive) {
return "T";
} else {
return "F";
}
}
private void setIsActiveValue(String isActive) {
this.isActive = "T".equals(isActive);
}
}
http://en.wikibooks.org/wiki/Java_Persistence/Basic_Attributes#Conversion
Which JPA provider do you use?
It seems that Hibernate has a separate annotation for this (#JoinColumnsOrFormula). To my knowledge, EclipseLink does not offer that annotation.
See related question on stackoverflow
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.