EntityManager merge using specific column other than id - java

I'm currently using Spring and Hibernate framework, and have an entity with:
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="ID")
private Long id;
#Column(name="ACC_ID")
private Long accId;
Now, in a specific case I'd like to merge an object in the database using column "ACC_ID" instead of "ID", however, I do not want to assign #Id to accId because I do not want to change the entity itself.
Is there anything I can do on the merge function? (But apparently merge takes no other parameter than an object)
entityManager.merge(entityObject)
Thanks in advance for any clue or help. =)

entityManager.merge(entityObject) can be used if it is your primary key based.
If it is another unique constraint you'd have to handle it by yourself. First try to find an entity with that value (with a query).
If a match is found, copy the primary key to your new entity before saving as normal.
For example:
public Entity save(Entity entity, boolean rollback) {
// look for a match, you'll have to implement your own method here
Entity match = getEntityByValue("column_name", entity.getMergeColumn());
if (match != null) {
// copy the primary key
entity.setId(match.getId());
}
// save the entity
save(entity, rollback);
}

Related

jooq - exclude id field when inserting from POJO

Is there anyway to insert a new record into a PostgreSQL database with Jooq straight from a POJO which extends a general identity class that has an id field without including the id in the insert statement?
An example POJO:
#Data
public abstract class PersistenceIdentity {
#Id
#Column(name = "id", unique = true, nullable = false, precision = 7, insertable = false)
private Integer id;
#Column(name = "created_date")
private LocalDateTime createdDate;
public abstract Table<R> getJooqTable();
}
#Data
public class SocialNetwork extends PersistenceIdentity {
#Column(name = "name")
private String name;
#Override
public Table<SocialNetworkRecord> getJooqTable() {
return Tables.SOCIAL_NETWORK;
}
}
The PostgreSQL schema is:
CREATE TABLE "social_network" (
id SERIAL NOT NULL PRIMARY KEY,
created_date TIMESTAMP DEFAULT now(),
name TEXT NOT NULL
);
My code to persist the POJO:
public <T extends PersistenceIdentity> T insertRecord(T record) {
Record newRecord = db.newRecord(record.getJooqTable(), record);
if (newRecord instanceof UpdatableRecord) {
((UpdatableRecord) newRecord).store();
}
return newRecord.into(record);
}
I realize I'm probably doing what Jooq really wasn't meant for (i.e. using generic types), however that (appears) to work just fine.
The problem is, Jooq includes the id in the insert statement and I then, of course, get a null value constraint. I don't want it inserted when it's a new record, however I do want it included when it returns the record (after inserting), when updating and also in select statements.
I can't simply exclude the id because I need it later on to easily get around some of the #OneToMany / #ManyToOne limitations.
And I would rather not have to insert the specific values for each POJO (that's why we annotated with #Column).
Does Jooq not honor the #Id or the insertable = false parameter in #Column?
Can anyone shed some light on this?
EDIT 1
Per request, below is the relevant snippet from the jOOQ generated table object. I'm not sure if this is correct or not for what I'm trying to do (i.e. allow the database to generate the ID), but I would think nextval('social_network_id_seq'::regclass) would accomplish that.
#Generated(
value = {
"http://www.jooq.org",
"jOOQ version:3.9.1"
},
comments = "This class is generated by jOOQ"
)
#SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class SocialNetwork extends TableImpl<SocialNetworkRecord> {
/**
* The column <code>public.social_network.id</code>.
*/
public final TableField<SocialNetworkRecord, Integer> ID = createField("id", org.jooq.impl.SQLDataType.INTEGER.defaultValue(org.jooq.impl.DSL.field("nextval('social_network_id_seq'::regclass)", org.jooq.impl.SQLDataType.INTEGER)), this, "");
}
Also, we use the mvn jooq-codegen:generate -Djooq.generator.name=org.jooq.util.XMLGenerator to generate the XML schema and then generate the the jOOQ table objects from that XML config. The thinking is we can push the XML config to github and all builds can simply regenerate the table objects from that.
Here is the XML:
<column>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
<column_name>id</column_name>
<data_type>integer</data_type>
<character_maximum_length>0</character_maximum_length>
<numeric_precision>32</numeric_precision>
<numeric_scale>0</numeric_scale>
<ordinal_position>1</ordinal_position>
<column_default>nextval('social_network_id_seq'::regclass)</column_default>
</column>
<table_constraint>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>social_network_pkey</constraint_name>
<constraint_type>PRIMARY KEY</constraint_type>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</table_constraint>
<table_constraint>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>2200_17431_1_not_null</constraint_name>
<constraint_type>CHECK</constraint_type>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</table_constraint>
<table_constraint>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>2200_17431_3_not_null</constraint_name>
<constraint_type>CHECK</constraint_type>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</table_constraint>
<key_column_usage>
<column_name>id</column_name>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>social_network_pkey</constraint_name>
<ordinal_position>0</ordinal_position>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</key_column_usage>
EDIT 2
My SocialNetwork jOOQ-generated table object does not have a getIdentity() method, however it does have a getPrimaryKey() method and if it helps, my SocialNetworkRecord class has two Constructors:
public SocialNetworkRecord() {
super(SocialNetwork.SOCIAL_NETWORK);
}
/**
* Create a detached, initialised SocialNetworkRecord
*/
public SocialNetworkRecord(Integer id, Timestamp createdDate, String name) {
super(SocialNetwork.SOCIAL_NETWORK);
set(0, id);
set(1, createdDate);
set(2, name);
}
The way jOOQ works, there are two elements worth explaining:
Step 1: Record.from(Object):
Record newRecord = db.newRecord(record.getJooqTable(), record);
This call is convenience for this:
Record newRecord = db.newRecord(record.getJooqTable());
newRecord.from(record);
And the Record.from(Object) will copy all values from the record to the newRecord by using Record.set(Field, Object), which again sets the record's internal Record.changed(Field) flag.
Step 2: UpdatableRecord.store()
Your call to:
((UpdatableRecord) newRecord).store();
Will take all changed() fields into consideration for the relevant INSERT or UPDATE statement that is executed. The rationale here is that people sometimes want to set the primary key value explicitly, and not let an identity generate the value for them. Even if an identity is present on the primary key, it may sometimes be desireable to override its value. SQL standard databases (e.g. Oracle 12c) thus support two ways of specifying an identity:
-- This can be overridden
GENERATED BY DEFAULT AS IDENTITY
-- This can never be overridden
GENERATED ALWAYS AS IDENTITY
(MySQL's AUTO_INCREMENT or PostgreSQL's SERIAL type work the same way)
jOOQ assumes GENERATED BY DEFAULT AS IDENTITY here. The only exception to the above behaviour is when the identity column is NOT NULL and the Record value for the identity is null and jOOQ's meta model is aware of both:
- `NOT NULL` constraint
- `GENERATED BY DEFAULT AS IDENTITY`
Then, jOOQ will omit considering the identity value for insertion / update.
Bug in 3.9.2 and less:
Note that up until jOOQ version 3.9.2, there was a bug / missing feature in the XMLGenerator that produces the XML file you're importing: https://github.com/jOOQ/jOOQ/issues/6141. This bug resulted in no identity information being generated.
Workaround 1: If you cannot influence the jOOQ meta model
If, for some reason, you cannot get the jOOQ meta model to reflect your NOT NULL constraint and your DEFAULT clause, you could work around this limitation by resetting the value of the identity right after your Record.from(Object) call using Record.reset(Field):
Record newRecord = db.newRecord(record.getJooqTable(), record);
newRecord.reset(identityColumn);
((UpdatableRecord) newRecord).store();
Workaround 2: Generate a synthetic identity
The code generator has a feature to generate synthetic identities. For instance, if all your identity columns are called ID, you could write this:
<!-- fully qualified -->
<syntheticIdentities>.*?\.ID</syntheticIdentities>
Or this:
<!-- unqualified -->
<syntheticIdentities>ID</syntheticIdentities>

JPA Mapping Multi-Rows with ElementCollection

I'm trying to follow the JPA tutorial and using ElementCollection to record employee phone numbers:
PHONE (table)
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Short version
What I need is a class like this:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#Embedded
List<Phone> phones;
}
that stores each person's phone numbers in a collection.
Long version
I follow the tutorial code:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#ElementCollection
#CollectionTable(
name="Phones",
joinColumns=#JoinColumn(name="owner_id")
)
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type")
String type = "";
#Column(name="number")
String number = "";
public Phone () {}
public Phone (String type, String number)
{ this.type = type; this.number = number; }
}
with a slight difference that I only keep one table. I tried to use the following code to add records to this table:
public static void main (String[] args) {
EntityManagerFactory entityFactory =
Persistence.createEntityManagerFactory("Tutorial");
EntityManager entityManager = entityFactory.createEntityManager();
// Create new entity
entityManager.getTransaction().begin();
Phone ph = new Phone("home", "001-010-0100");
PhoneId phid = new PhoneId();
phid.phones.add(ph);
entityManager.persist(phid);
entityManager.getTransaction().commit();
entityManager.close();
}
but it keeps throwing exceptions
Internal Exception: org.postgresql.util.PSQLException: ERROR: null
value in column "type" violates not-null constraint Detail: Failing
row contains (0, null, null). Error Code: 0 Call: INSERT INTO Phones
(owner_id) VALUES (?) bind => [1 parameter bound] Query:
InsertObjectQuery(tutorial.Phone1#162e295)
What did I do wrong?
Sadly, i think the slight difference that you only keep one table is the problem here.
Look at the declaration of the PhoneId class (which i would suggest is better called PhoneOwner or something like that):
#Entity
#Table(name="Phones")
public class PhoneId {
When you declare that a class is an entity mapped to a certain table, you are making a set of assertions, of which two are particularly important here. Firstly, that there is one row in the table for each instance of the entity, and vice versa. Secondly, that there is one column in the table for each scalar field of the entity, and vice versa. Both of these are at the heart of the idea of object-relational mapping.
However, in your schema, neither of these assertions hold. In the data you gave:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
There are two rows corresponding to the entity with owner_id 1, violating the first assertion. There are columns TYPE and NUMBER which are not mapped to fields in the entity, violating the second assertion.
(To be clear, there is nothing wrong with your declaration of the Phone class or the phones field - just the PhoneId entity)
As a result, when your JPA provider tries to insert an instance of PhoneId into the database, it runs into trouble. Because there are no mappings for the TYPE and NUMBER columns in PhoneId, when it generates the SQL for the insert, it does not include values for them. This is why you get the error you see - the provider writes INSERT INTO Phones (owner_id) VALUES (?), which PostgreSQL treats as INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null), which is rejected.
Even if you did manage to insert a row into this table, you would then run into trouble on retrieving an object from it. Say you asked for the instance of PhoneId with owner_id 1. The provider would write SQL amounting to select * from Phones where owner_id = 1, and it would expect that to find exactly one row, which it can map to an object. But it will find two rows!
The solution, i'm afraid, is to use two tables, one for PhoneId, and one for Phone. The table for PhoneId will be trivially simple, but it is necessary for the correct operation of the JPA machinery.
Assuming you rename PhoneId to PhoneOwner, the tables need to look like:
create table PhoneOwner (
owner_id integer primary key
)
create table Phone (
owner_id integer not null references PhoneOwner,
type varchar(255) not null,
number varchar(255) not null,
primary key (owner_id, number)
)
(I've made (owner_id, number) the primary key for Phone, on the assumption that one owner might have more than one number of a given type, but will never have one number recorded under two types. You might prefer (owner_id, type) if that better reflects your domain.)
The entities are then:
#Entity
#Table(name="PhoneOwner")
public class PhoneOwner {
#Id
#Column(name="owner_id")
long id;
#ElementCollection
#CollectionTable(name = "Phone", joinColumns = #JoinColumn(name = "owner_id"))
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type", nullable = false)
String type;
#Column(name="number", nullable = false)
String number;
}
Now, if you really don't want to introduce a table for the PhoneOwner, then you might be able to get out of it using a view. Like this:
create view PhoneOwner as select distinct owner_id from Phone;
As far as the JPA provider can tell, this is a table, and it will support the queries it needs to do to read data.
However, it won't support inserts. If you ever needed to add a phone for an owner who is not currently in the database, you would need to go round the back and insert a row directly into Phone. Not very nice.

#OneToOne-Relation with "is not null"

I'm using JPA2, Unitils, jUnit + other stuff.
My problem concerns two entities:
#Entity
public class CaseStuff implements Serializable {
....
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long caseStuffId;
#OneToOne(mappedBy = "CaseStuff")
private XCase xcase;
...
}
#Entity
public class XCase implements Serializable {
....
#OneToOne
#JoinColumn(name = "CASESTUFFID")
private CaseStuff caseStuff;
....
}
Both tables got the ID of the other table, so it could also be mapped the other way around.
In a jUnit-Test, Unitils inserts one CaseStuff record with no XCase. I confirmed, that it really is null.
BUT, then I use the following query:
"select s from CaseStuff s where s.xcase is null"
and it returns 0 CaseStuff objects. Doing the same with "is not null" returns the object, but when inspecting CaseStuff.xcase while debugging, it is clearly null.
Any idea, what's going awry here?
EDIT:
The SQL generated by Hibernate translates to
select
*
from
CaseStuff casestuff0_
where
(
casestuff0_.xcaseid is null
)
*Replaced all field names by *
EDIT2:
Before I changed it to a OneToOne-Relation, it was an unneccessary ManyToOne.
Previously the jUnit test had the query
"select s from CaseStuff s where not exists (select x from s.xcase x)"
This still works correctly for some reason, as if s.xcase was still a Set.
Thanks to all who try to figure it out with me.
As you defined your entities Xcase will have the foreign key
So Xcase table beside it's id will have caseStuff_id (or other name) as foreign key to the Xcase id.
Like in this image (make abstraction of the names)
In your case Person is Xcase and CaseStuff is PersonDetails
So probably you CaseStuff table (if it was generated by the persistence provider does not have a case id)
Please check on the db level to see how your tables are defined, structurally speaking.

ManyToOne Cascade causes repeated persists in the mother table

I have two entities: Questionnaire and QuestionnaireTime. Questionnaire's id is a foreign key in QuestionnaireTime. So the relationship in my QuestionnaireTime entity looks like this:
#JoinColumn(name = "questionnaireid", referencedColumnName = "id")
#ManyToOne(cascade = CascadeType.PERSIST)
private Questionnaire questionnaireid;
So what I'm trying to do is to add multiple QuestionnaireTime records for one Questionnaire. If I remove the CascadeType.PERSIST part in my relationship, my persist is not done. And when I use cascade, I get several new records in my main table Questionnaire and that's not what I want.
For example when I want to add three QuestionnaireTime's for a certain Questionnaire, the three records are inserted in my QuestionnaireTime table but also 3+1 records are added in Questionnaire.
If you need more explanation. This is my managed bean, the part that I'm trying to add multiple QuestionnaireTime records in one Questionnaire:
NB - current is my Questionnaire object
else if (current.getType().equals("frequent")) {
int iteration = 1;
currentQuestionnaireTime = new QuestionnaireTime();
if (!selectDateList.isEmpty()) {
for (String insertedDate : selectDateList) {
currentQuestionnaireTime.setId(0);
currentQuestionnaireTime.setQuestionnaireid(current);
getEjbQuestionnaireTimeFacade().create(currentQuestionnaireTime);
iteration++;
}
}
}
try {
getFacade().create(current); // my Questionnaire facade
} catch (EJBException ejbe) {
ejbe.getCause();
}
A few things,
questionnaireid - this is a very bad field name, questionnaire would make sense.
currentQuestionnaireTime.setId(0); - you should not be changing the id of an existing object, instead create a new object
getEjbQuestionnaireTimeFacade().create() - what does this do? If you need the reference to the current, then the current should be persisted first. If you EJB remote? If it is, then either make it local, or ensure you use merge() not persist(), as you new object has a reference to a detached object. Or find the reference in the current persistence context.

JPA Lookup Table Values

I've got 2 tables. One for questions and one for possible answers. For example, I'm modelling:
"Do you own a dog?" Yes[ ], No[ ].
So I have a set of questions and a set of possible answers. What I want to know how do I represent this in JPA (note this is not about capturing the answer, but displaying the question and populating a selection box).
So far I have:
#Entity(name="QUESTIONS")
public class Question {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private int order;
private String questionTitle;
private String questionText;
private Set<AnswerOption> possibleAnswers;
....
}
It is the private Set<AnswerOption> possibleAnswers; part I'm having trouble with. How do I get this to be pre-populated with the possible range of answers?
The way it is modelled above will provide a Set variable to store answers in.
Am I thinking about this the wrong way? Should I use code to populate the database and assign the same AnswerOption object(s) to different Question objects?
Thanks for any help.
Adam
As Hibernate is essentially an ORM tool, it just takes care of mapping your Question and AnswerOption classes and instances to the defined tables.
What you need is to initialize data, not data structure. So, you've got to populate all of your Question instances with their possible AnswerOption instances in some sort of initQuestions() initialization method.
Also, you'd better note whether these Questions are already initialized.
You should look at this first from a database relational modeling point of view, before trying to map that relationship in JPA. First, you need to define how questions and answers are linked at the database level. I presume you use a foreign key or association table. Something like this:
CREATE TABLE questions (
id INT NOT NULL PRIMARY KEY,
text VARCHAR(255)
);
CREATE TABLE answers (
id INT NOT NULL PRIMARY KEY,
text VARCHAR(255)
);
CREATE TABLE question_answers (
id INT NOT NULL PRIMARY KEY,
question_id INT NOT NULL,
answer_id INT NOT NULL,
KEY k_question_id (question_id),
KEY k_answer_id (answer_id),
CONSTRAINT fk_question_answers_question_id FOREIGN KEY (question_id)
REFERENCES questions(id),
CONSTRAINT fk_question_answers_answer_id FOREIGN KEY (answer_id)
REFERENCES answers(id)
);
This defines the relationship between questions and answers as an association table, which you can then map in JPA thusly:
#Entity(name="QUESTIONS")
public class Question {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private int order;
private String questionTitle;
private String questionText;
#JoinTable(name = "question_answers", joinColumns = { #JoinColumn(name = "question_id", unique = true) }, inverseJoinColumns = { #JoinColumn(name = "answer_id") })
private Set<AnswerOption> possibleAnswers;
}
The annotations for using a foreign key (as opposed to an association table) will be different of course, if you want to go that route leave a comment and I'll whip up an example. This should be enough to get you started.

Categories