Mapping from table to entity on non-unique column - entity-framework

I have a table definition which looks like this. I do not have the luxury to change the column defn.
table definition
Table FOO{
F_ID NUMBER, -- PK
CODE VARCHAR2(10), -- NON UNIQUE
F_NAME VARCHAR2(10)
}
Table BAR{
B_ID NUMBER, -- PK
CODE VARCHAR2(10), --NON UNIQUE
B_NAME VARCHAR2(10)
}
I need to map this to two entities.
Please note that the relationship is one to many. that is, one Foo can have multiple Bars from an entity point of view.
However,
the same code, which is the common column is non unique in both tables, and can have multiple values in both tables.
I know this can be solved using a join afterwards, but the Bar information is required for every Foo object that gets loaded up.
Is there a way to map this naturally using ORM using a join on the common column code?
#Entity
#Table(name = "FOO")
class Foo{
#Id
#Column(name = "F_ID", nullable = false)
private Long id;
#Column(name = "F_NAME")
private String fName;
#Column(name = "CODE", nullable = false)
private String code;
//What should be the join condition?
private List<Bar> bars;
}
#Entity
#Table(name = "BAR")
class Foo{
#Id
#Column(name = "B_ID", nullable = false)
private Long id;
#Column(name = "B_NAME")
private String bName;
//Is this required?
//private String code;
}

Related

Handling reduntant columns with hibernate/jpa/spring data

i'm kinda struggling mapping the following schema with hibernate
table_a (A1_ID,A2_ID) --> PK = (A1_ID, A2_ID)
table_b (A1_ID, A2_ID, B1_ID) --> PK =(A1_ID, A2_ID, B1_ID)
where table_b's A1_ID and A2_ID should be foreingkey referencing respective table_A's columns
There is a one-to-many from TABLE_A to TABLE_B where TABLE_B's primary key is partially shared with TABLE_A's primary key
What I've tried so far
#Data
#Entity
#Table(name = "table_a")
#IdClass(TableA.TableAKey.class)
public class TableA {
#Id
#Column(name = "A1_ID)
private String a1_id;
#Id
#Column(name = "A2_ID)
private String a2_id;
#OneToMany(mappedBy = "tableA",fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<TableB> tableB;
#Data
static class TableAKey implements Serializable {
private String a1_id;
private String a2_id
}
}
**CHILD ENTITY**
#Data
#Entity
#Table(name = "table_b")
#IdClass(TableB.TableBKey.class)
public class TableB {
#Id
#Column(name = "B1_ID)
private String b1_id;
#Id
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "a1_id", insertable = false, updatable = false),
#JoinColumn(name = "a2_id", insertable = false, updatable = false)
)}
private TableA tableA;
#Column(name = "A1_ID)
private String a1_id;
#Column(name = "A2_ID)
private String a2_id;
#Data
static class TableAKey implements Serializable {
private String b1_id;
private TableA tableA;
}
}
I was expecting i could be able to do something like this:
TableA tableA = new TableA();
t.setA1_id("a1id");
t.setA2_id("a2id");
TableB tableB = new TableB();
tableB.setB1Id("b1Id");
tableA.setTableB(Arrays.asList(tableB));
tableARepository.save(tableA);
And the code above I was expecting to "magically" perform the following insert at DB
INSERT INTO table_A (A1_ID,A2_ID) VALUES ('a1id',a2id');
INSERT INTO table_B (A1_ID,A2_ID, B1_ID) VALUES ('a1id',a2id','b1id')
but instead i get a "the column index is out of range: n, number of columns n-1".
I also tried with some embeddedId approach, using referenceColumnName but nothing.
Am I doing something wrong in the mapping or in the object creation process?
The problem is a lot similar to the following
https://hibernate.atlassian.net/browse/HHH-14340

JAP: table column with name id but not primary key

I have a table with the columns vendorid and id (and more, omitted here; using lombok for definition):
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "vendor_store")
public class VendorMeta {
#Id
#Column(name = "vendorid", unique = true)
private String vendorid;
#Column(name = "id", unique = false)
private String id;
}
This is the corresponding repository:
#Repository
public interface VendorMetaRepository extends JpaRepository<VendorMeta, String>, JpaSpecificationExecutor<VendorMeta> {
List<VendorMeta> findByVendorid(String vendorid);
}
I would expect that findByVendorid is returning a single element and findById returns a list, but it's working the opposite way:
Optional vendorMeta = vendorMetaRepository.findById("1");
List vendorMeta2 = vendorMetaRepository.findByVendorid("1");
Both methods return answers for searching vendorid, findbyId is also searching column vendorid.
What do I have to do to get the correct results?
It seems that this is not possible (at least when using lombok). The generated method findById() searches the primary key (whatever the name of this column is) and not the column with the name "id".
It is possible to use any column name for the primary key but not the name "id" for a non-primary-key column.

JPA NamedQuery with Join returning empty list

I've been struggling with a namedquery for a few days. The named query has an inner join to a 2nd table. One added complexity is that the primary key on the 2nd table is a composite key. I have the two tables simplified here:
Table: aname
nameIdx number(9),
firstName varchar2(40),
lastName varchar2(40),
primary key is nameIdx
Table: aname_role
nameIdx number(9), --foreign key to name table
nameType char(2),
inactiveFlag char(1)
composite primary key is on nameIdx and nameType
I am trying to emulate the following sql query in JPQL:
select * from aname n
left join aname_role nr on n.nameidx=nr.nameidx
where nr.nametype='5'
and nr.inactiveflag='N';
This query works as expected in Oracle returning many records. In Java I have these JPA entities:
#Entity
#Table(name="ANAME")
#NamedQueries({
#NamedQuery(name = "AName.findActiveSalesPersons", query = "SELECT a FROM AName a LEFT JOIN a.aNameRoleList r WHERE r.inactiveflag='N' and r.ANameRolePK.nametype='5' ")})
public class AName implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "NAMEIDX")
private Integer nameidx;
#Column(name = "FIRSTNAME")
private String firstname;
#Column(name = "LASTNAME")
private String lastname;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "aName")
private List<ANameRole> aNameRoleList;
//getters and setters here
and
#Entity
#Table(name = "ANAME_ROLE")
public class ANameRole implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected ANameRolePK aNameRolePK;
#Basic(optional = false)
#NotNull
#Column(name = "INACTIVEFLAG")
private Character inactiveflag;
#JoinColumn(name = "NAMEIDX", referencedColumnName = "NAMEIDX", insertable = false, updatable = false)
#ManyToOne(optional = false)
private AName aName;
//getters and setters here
There is also a primary key class ANameRolePK
#Embeddable
public class ANameRolePK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "NAMEIDX")
private int nameidx;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 2)
#Column(name = "NAMETYPE")
private String nametype;
//getters and setters here
With this setup, including the named query specified in the AName entity above, the following returns an empty result list:
em.createNamedQuery("AName.findActiveSalesPersons").getResultList();
Can anyone point me to what I am doing wrong in this named query?
SELECT a FROM AName a LEFT JOIN a.aNameRoleList r WHERE r.inactiveflag='N' and r.aNameRolePK.nametype='5'
Thanks,
Steve
By default, at least using Hibernate, the default fetch type is Lazy, so you need to do a join fetch instead of a join. Also, you should have select distinct. Try:
SELECT distinct a FROM AName a LEFT JOIN fetch a.aNameRoleList r WHERE r.inactiveflag='N' and r.aNameRolePK.nametype='5'
References: Default fetch type for one-to-one, many-to-one and one-to-many in Hibernate
After more testing, I realized the join was working, but not the "r.aNameRolePK.nametype='5'". But if I changed that to "r.aNameRolePK.nameidx=1" it works. So, it was just the nametype field, which we have defined as a char(2) in the database. The problem is with the spaces in a char field and it is discussed here: Java NamedQuery String Problem. It looks like the recommended way to resolve this is to implement an EclipseLink SessionCustomizer. For testing I changed the named query to
SELECT a
FROM AName a LEFT JOIN a.aNameRoleList r
WHERE r.inactiveflag='N' and trim(trailing from r.aNameRolePK.nametype)=5
This returns the expected records.

JPA -- Using the one-to-one dependency relationship on insertion

I have 2 entity classes with one-to-one dependencies on their primary keys:
The primary table:
#Entity
#Table(name = "tablePrimary")
#XmlRootElement
//...
public class TablePrimary implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#Column(name = "code")
private String code;
// set the dependency of table2 to this class
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Table2 table2inst;
// ...
} // end of class TablePrimary
The dependent table:
#Entity
#Table(name = "table2")
#XmlRootElement
//...
public class Table2 implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#Column(name = "name")
private String name;
#MapsId
#OneToOne(mappedBy = "table2inst")
#JoinColumn(name = "id")
private TablePrimary tablePrimaryInst;
//...
} // end of class Table2
Whenever there is a row with say, id==55 in TablePrimary, there is
a row with the same id==55 in Table2 and vice-versa.
So in essence, these two tables are one table in logical level-- split into 2 physical tables for practicality.
When i'm inserting a row into the "logical" table,
i first am inserting to TablePrimary-- the primary table in the relationship,
getting the value of id==55 field of this new row i just inserted and inserting a row to
Table2 with that id value.
As part of this, i'm checking, just in case,
whether a row with id==55 is already in Table2.
Is there a better way of doing this?
Does JPA have a feature to make these two insertions to these two physical tables
by using the 1-1 dependency I configured on them-- without me having to do it "manually" in the code? Or a control feature on the id fields of the tables I set the dependency on?
If there is-- how is done? how does it handle the key value collision in the dependent table-- Table2?
A similar thing will come up on deletion. However, i'm not there yet, and might figure out out of this.
TIA.
You can take advantage of JPA cascading. You will have to define a cascade on the owning side (the one with the join column). If you have set the owning side of the relationship and persist the owning side, the inverse side will be persisted as well:
TablePrimary tp = new TablePrimary();
Table2 t2 = new Table2();
t2.setTablePrimaryInst(tp);
entityManager.persist(t2);
The 'mappedBy' element is supposed to be placed on the inverse side. You entities could look like this:
public class Table2 ...
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "tp_id")
private TablePrimary tablePrimary;
public class TablePrimary...
#OneToOne(mappedBy="tablePrimary")
private Table2 table2;

Persisting a List of Integers with JPA?

We have a pojo that needs to have a list of integers. As an example, I've created a Message pojo and would like to associate a list of groupIds (these ids need to be queried and displayed in the UI). So ideally, we would like to be able to do something like this:
Message msg = em.find(Message.class, 101);
List<Integer> groupIds = msg.getGroupIds();
I was under the impression that this would require only one pojo with JPA, but according to the discussion here, I need to create a second pojo because JPA works in terms of objects instead of primitive types.
From that discussion I've tried the following example code, but I get the error openjpa-1.2.3-SNAPSHOT-r422266:907835 fatal user error: org.apache.openjpa.util.MetaDataException: The type of field "pojo.Group.messageId" isn't supported by declared persistence strategy "ManyToOne". Please choose a different strategy.
DDL:
CREATE TABLE "APP"."MESSAGE" (
"MESSAGE_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
"AUTHOR" CHAR(20) NOT NULL
);
ALTER TABLE "APP"."MESSAGE" ADD CONSTRAINT "MESSAGE_PK" PRIMARY KEY ("MESSAGE_ID");
CREATE TABLE "APP"."GROUP_ASSOC" (
"GROUP_ID" INTEGER NOT NULL,
"MESSAGE_ID" INTEGER NOT NULL
);
ALTER TABLE "APP"."GROUP_ASSOC" ADD CONSTRAINT "GROUP_ASSOC_PK" PRIMARY KEY ("MESSAGE_ID", "GROUP_ID");
ALTER TABLE "APP"."GROUP_ASSOC" ADD CONSTRAINT "GROUP_ASSOC_FK" FOREIGN KEY ("MESSAGE_ID")
REFERENCES "APP"."MESSAGE" ("MESSAGE_ID");
POJOs:
#Entity
#Table(name = "MESSAGE")
public class Message {
#Id
#Column(name = "MESSAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long messageId;
#OneToMany
private List<Group> groups = new ArrayList<Group>();
#Column(name = "AUTHOR")
private String author;
// getters/setters ommitted
}
#Entity
#IdClass(pojo.Group.GroupKey.class)
#Table(name = "GROUP_ASSOC")
public class Group {
#Id
#Column(name = "GROUP_ID")
private Long groupId;
#Id
#Column(name = "MESSAGE_ID")
#ManyToOne
private Long messageId;
public static class GroupKey {
public Long groupId;
public Long messageId;
public boolean equals(Object obj) {
if(obj == this) return true;
if(!(obj instanceof Group)) return false;
Group g = (Group) obj;
return g.getGroupId() == groupId && g.getMessageId() == messageId;
}
public int hashCode() {
return ((groupId == null) ? 0 : groupId.hashCode())
^ ((messageId == null) ? 0 : messageId.hashCode());
}
}
// getters/setters ommitted
}
Test Code:
EntityManager em = Persistence.createEntityManagerFactory("JPATest").createEntityManager();
em.getTransaction().begin();
Message msg = new Message();
msg.setAuthor("Paul");
em.persist(msg);
List<Group> groups = new ArrayList<Group>();
Group g1 = new Group();
g1.setMessageId(msg.getMessageId());
Group g2 = new Group();
g2.setMessageId(msg.getMessageId());
msg.setGroups(groups);
em.getTransaction().commit();
This all seems ridiculous -- 3 classes (if you include the GroupKey composite identity class) to model a list of integers -- isn't there a more elegant solution?
This is an old topic but things have changed since OpenJPA2, now you can directly persist primitive types or String object. Use ElementCollection annotation to use simple one-to-many linking, no need to intermediate object or link tables. This is how most of us probably create SQL schemas.
#Entity #Table(name="user") #Access(AccessType.FIELD)
public class User {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private long id; // primary key (autogen surrogate)
private String name;
// ElementCollection provides simple OneToMany linking.
// joinColumn.name=foreign key in child table. Column.name=value in child table
#ElementCollection(fetch=FetchType.LAZY)
#CollectionTable(name="user_role", joinColumns={#JoinColumn(name="user_id")})
#Column(name="role")
private List<String> roles;
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name=name; }
public List<String> getRoles() { return roles; }
public void setRoles(List<String> roles) { this.roles=roles; }
}
- - -
CREATE TABLE user (
id bigint NOT NULL auto_increment,
name varchar(64) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY USERNAME (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
CREATE TABLE user_role (
user_id bigint NOT NULL,
role varchar(64) NOT NULL default '',
PRIMARY KEY (user_id, role)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
I really think that what you have is in fact a many-to-many association between two Entities (let's call them Message and Group).
The DDL to represent this would be:
CREATE TABLE "APP"."MESSAGE" (
"MESSAGE_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
"AUTHOR" CHAR(20) NOT NULL
);
ALTER TABLE "APP"."MESSAGE" ADD CONSTRAINT "MESSAGE_PK" PRIMARY KEY ("MESSAGE_ID");
CREATE TABLE "APP"."GROUP" (
"GROUP_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1)
);
ALTER TABLE "APP"."GROUP" ADD CONSTRAINT "GROUP_PK" PRIMARY KEY ("GROUP_ID");
CREATE TABLE "APP"."MESSAGE_GROUP" (
"GROUP_ID" INTEGER NOT NULL,
"MESSAGE_ID" INTEGER NOT NULL
);
ALTER TABLE "APP"."MESSAGE_GROUP" ADD CONSTRAINT "MESSAGE_GROUP_PK" PRIMARY KEY ("MESSAGE_ID", "GROUP_ID");
ALTER TABLE "APP"."MESSAGE_GROUP" ADD CONSTRAINT "MESSAGE_GROUP_FK1" FOREIGN KEY ("MESSAGE_ID")
REFERENCES "APP"."MESSAGE" ("MESSAGE_ID");
ALTER TABLE "APP"."MESSAGE_GROUP" ADD CONSTRAINT "MESSAGE_GROUP_FK2" FOREIGN KEY ("GROUP_ID")
REFERENCES "APP"."MESSAGE" ("GROUP_ID");
And the annotated classes:
#Entity
public class Message {
#Id
#Column(name = "MESSAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long messageId;
#ManyToMany
#JoinTable(
name = "MESSAGE_GROUP",
joinColumns = #JoinColumn(name = "MESSAGE_ID"),
inverseJoinColumns = #JoinColumn(name = "GROUP_ID")
)
private List<Group> groups = new ArrayList<Group>();
private String author;
//...
}
#Entity
public class Group {
#Id
#GeneratedValue
#Column(name = "GROUP_ID")
private Long groupId;
#ManyToMany(mappedBy = "groups")
private List<Message> messages = new ArrayList<Message>();
//...
}
I'm not sure you need a bi-directional association though. But you definitely need to start to think object if you want to use JPA (in you're example, you're still setting ids, you should set Entities). Or maybe JPA is not what you need.
isn't there a more elegant solution?
I'm not sure "elegant" is appropriate but JPA 2.0 defines an ElementCollection mapping (as I said in my previous answer):
It is meant to handle several non-standard relationship mappings. An ElementCollection can be used to define a one-to-many relationship to an Embeddable object, or a Basic value (such as a collection of Strings).
But that's in JPA 2.0. In JPA 1.0, you would have to use a provider specific equivalent, if your provider does offer such an extension. It appears that OpenJPA does with #PersistentCollection.
Based on your schema you have a ManyToOne relationship between Group and Message. Which means that a single Message can belong to multiple groups, but each group can have a single message.
The entities would look something like this.
#Entity
#Table(name = "GROUP_ASSOC")
public class Group {
#Id
#Column(name="GROUP_ID")
private int id;
#ManyToOne
#Column(name="MESSAGE_ID")
#ForeignKey
private Message message;
// . . .
}
#Entity
public class Message {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "MESSAGE_ID")
private int id;
#Column(length=20)
private String author;
#OneToMany(mappedBy="message")
private Collection<Group> groups;
}
There's no need for an IDClass in your app (you only need one if your ID is contains multiple columns).
To get the groupIds for a given message you could write a query like this one
Query q = em.createQuery("Select g.id from Group g where g.message.id = :messageId");
q.setParameter("messageId", 1);
List results = q.getResultList();
Or just iterate over Message.getGroups() :
Message m = em.find(Message.class, 1);
for(Group g : m.getGroups()) {
// create a list, process the group whatever fits.
}