JPA secodaryTables with composite key - jpa

I'm working on a legacy database schema. On this schema there are 2 tables:
clienti 1->many clienti_soc
clienti has the primary key id_cliente, clienti_soc has composite key (id_cliente, id_societa). In my JPA application I need to pick the record of clienti_soc with a specific value of id_societa I'd like to put in my application.properties.
Here how I mapped my entity
#Entity
#Table(name="va_clienti")
#SecondaryTable(name = "va_clienti_soc", pkJoinColumns = #PrimaryKeyJoinColumn(name = "id_cliente"))
#Data
public class Cliente {
#Id
public String idCliente;
public String ragioneSociale;
....
#Column(table = "va_clienti_soc" , name="id_societa")
public String idSocieta = "1"; // <-- ????????
#Column(table = "va_clienti_soc" , name="id_gr_prezzi_partner")
public String idGrPrezziPartner;
}
is there a better way or any best practice to crerate the JPA enity? Is there a cleverer use of #PrimaryKeyJoinColumn annotation?

Related

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 repository retrieve custom primary key value after save

I have an entity class pointing to postgresql table. Below is table structure. The paymentreferencenumber is the PK which is populated by a trigger. id field is the sequence generated field. When i try to save in this table using JPARepository save method it inserts the first record. But after that it fails due to the primary key constraint. Since PK is a string type and generated using trigger I am specifying generator strategy as 'select'. Can anyone help me with this blocker and point me in the right direction. Thanks
Table structure --
custId serial not null,
paymentreferencenumber varchar(32) not null
constraint customers1_pkey
primary key,
firstname varchar(255),
lastname varchar(255)
Entity class --
#Entity
#Table(name = "customersnew")
public class Customer implements Serializable {
private static final long serialVersionUID = -1L;
#GeneratedValue(generator = "seq")
#GenericGenerator(name="seq", strategy="sequence", parameters = { #Parameter(name="key", value = "customersnew_custid_seq")})
#Column(name = "custid")
private long id;
#Id
#GeneratedValue(generator = "trigger_generated")
#GenericGenerator(name="trigger_generated", strategy="select", parameters = { #Parameter(name="key", value = "id")})
#Column(name = "paymentreferencenumber")
private String refNum;
#Column(name = "firstname")
private String firstName;
#Column(name = "lastname")
private String lastName;
}
--- Controller using JPA save
#RestController
public class CustomerController {
#Autowired
CustomerRepository repository;
EntityManagerFactory emf;
public CustomerController(CustomerRepository repository, EntityManagerFactory emf) {
this.repository = repository;
this.emf = emf;
}
#PostMapping("/create")
public String create(#RequestBody CustomerUI customer){
// save a single Customer
Customer returnObj = repository.saveAndFlush(new Customer(customer.getFirstName(), customer.getLastName()));
PersistenceUnitUtil util = emf.getPersistenceUnitUtil();
Object retObj = util.getIdentifier(returnObj);
return "Customer is created";
}
If you don't specify an id generation strategy, Hibernate will use GenerationType.AUTO. This will result in any of
AUTO - either identity column, sequence or table depending on the
underlying DB.
If you look here, you'll notice all of those generate ids of type long, short or int, not of type String.
Say you wanted a String UUID as an id, you could use
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "paymentreferencenumber")
private String refNum;

Duplicate keys with hibernate

I have a Postgres 9.5.5 table named mytable under schema myschema. It does not have a primary key - we missed adding it when the table was created. This is causing rows with duplicate values in id column of table. The only access points to this table are through the save(), update() and delete() methods of the Hibernate 4.3.10 Final entity class. Nobody is manually updating the database far as I know. What part of the code is sending duplicate id column values to the table? The entity class looks like this -
#Entity
#Table(name = "mytable", schema = "myschema")
public class MyTable implements Serializable {
/** Id. */
#Id
#GeneratedValue(generator = "myschema.mytable_seq", strategy = GenerationType.AUTO)
#SequenceGenerator(name = "myschema.mytable_seq", sequenceName = "myschema.mytable_seq")
#Column(name = "id", unique = true, nullable = false)
private int id;
Below is the sequence definition in postgres -
CREATE SEQUENCE myschema.mytable_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 765
CACHE 1;
The hibernate code is something like this (sessionFactory is an autowired instance of org.hibernate.SessionFactory -
#Repository
public class HBMyTableDao extends HBAbstractDAO<MyTable> implements MyTableDao {
public void save(MyTable model) {
sessionFactory.getCurrentSession().save(model);
}
public void saveOrUpdate(MyTable model) {
sessionFactory.getCurrentSession().saveOrUpdate(model);
}
public void update(MyTable model) {
sessionFactory.getCurrentSession().update(model);
}
public void delete(MyTable model) {
sessionFactory.getCurrentSession().delete(model);
}
}
Hibernate requires primary key.
see https://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/#mapping-declaration-id
Maybe you can add a autogenerated column to the table

The type of field isn't supported by declared persistence strategy "OneToMany"

We are new to JPA and trying to setup a very simple one to many relationship where a pojo called Message can have a list of integer group id's defined by a join table called GROUP_ASSOC. Here is the DDL:
CREATE TABLE "APP"."MESSAGE" (
"MESSAGE_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1)
);
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");
Here is the pojo:
#Entity
#Table(name = "MESSAGE")
public class Message {
#Id
#Column(name = "MESSAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int messageId;
#OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
private List groupIds;
public int getMessageId() {
return messageId;
}
public void setMessageId(int messageId) {
this.messageId = messageId;
}
public List getGroupIds() {
return groupIds;
}
public void setGroupIds(List groupIds) {
this.groupIds = groupIds;
}
}
I know this is wrong as there is no #Column mapping to GROUP_ASSOC.GROUP_ID for the groupIds property, but hopefully this illustrates what we are trying to do. When we run the following test code we get <openjpa-1.2.3-SNAPSHOT-r422266:907835 fatal user error> org.apache.openjpa.util.MetaDataException: The type of field "pojo.Message.groupIds" isn't supported by declared persistence strategy "OneToMany". Please choose a different strategy.
Message msg = new Message();
List groups = new ArrayList();
groups.add(101);
groups.add(102);
EntityManager em = Persistence.createEntityManagerFactory("TestDBWeb").createEntityManager();
em.getTransaction().begin();
em.persist(msg);
em.getTransaction().commit();
Help!
When you are working with JPA, you should think Object and relations between Objects and you should map your Object model, not ids, to your relational model (it is possible to map a List of basic values with #ElementCollection in JPA 2.0 though but what I said just before still applies).
Here, (assuming this really is a one-to-many relation between Message and GroupAssoc and not a many-to-many relation between Message and Group entities) you should have something like this:
#Entity
#Table(name = "MESSAGE")
public class Message implements Serializable {
#Id
#Column(name = "MESSAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long messageId;
#OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
private List<GroupAssoc> groupAssocs = new ArrayList<GroupAssoc>();
public Long getMessageId() {
return messageId;
}
public void setMessageId(Long messageId) {
this.messageId = messageId;
}
public List<GroupAssoc> getGroupAssocs() {
return groupAssocs;
}
public void setGroupAssocs(List<GroupAssoc> groupAssocs) {
this.groupAssocs = groupAssocs;
}
// equals() and hashCode()
}
And another entity for GroupAssoc.
PS: Your DDL really looks like a (M:N) relation between MESSAGE and GROUP (or I don't understand the PK constraint of GROUP_ASSOC) but you didn't show any FK constraint on GROUP_ID so I'm not 100% sure. But if that's the case, then you should use an #ManyToMany instead of #OneToMany.

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.
}