Hibernate and JPA annotations - persist a list of phrases - java

I'm having trouble using hibernate to persist a simple java object containing just a list of phrases in the form of strings.
To store this in a MySql database I have two tables:
phrase_list - columns:
group_id, phrase_id, list_position (which together make up the primary key; also there is a foreign key constraint on phrase_id from the phrase table)
phrase - columns: phrase_id, phrase (phrase is a varchar with a unique index)
I would like to simply be able to annotate the object containing the list of phrases in order to persist it, but nothing I've tried has worked, I've tried examples and variations of using #ElementCollection and #OneToMany with another Entity representing the phrase, but no matter what I try I can't seem to get this to work.
For example a phrase list: {"a","b","b"} would be stored as:
**(phrase table)** **(phrase_list table)**
phrase_id phrase group_id phrase_id list_position
1 'a' 1 1 0
2 'b' 1 2 1
1 2 2
Here is the attempt to what I thought would work; I added an extra id field because I could see no way to use the foreign key as part of the id, and annotating the list with #Id didn't work.
#Entity #Table(name = "phrase_list")
public class PhraseList {
#ElementCollection
#CollectionTable(
name="phrase",
joinColumns=#JoinColumn(name="phrase_id")
)
#OrderColumn(name="list_position")
#Column(name="phrase")
private List<String> phrases;
#Id #GeneratedValue
#Column(name = "list_id")
private Long id;
#Column(name = "goup_id")
private Long groupId;
public void setResults(List<String> results){
this.results = results;
}
public void setGroupId(Integer groupId) {
this.groupId = groupId;
}
}
I get this exception:
org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Cannot add or update a child row: a foreign key constraint fails (test_db.phrase_list, CONSTRAINTfk_phrase_list_phrase_idFOREIGN KEY (phrase_id) REFERENCESphrase(phrase_id`) ON DELETE NO ACTION ON UPDATE )
Is there any way to get this to work simply by annotating the plain Java object? If not what is the best way to achieve this?
If I can't get it to work with annotation I would probably write custom queries and/or specialized methods for persisting the object. I.e., a method that is coupled to that particular class in knowing how to persist its objects, which I hoped to avoid.

Related

Hibernate join-table two composite keys that are both foreign keys

I have two different tables, both of which have composite embedded keys. Both composite keys have in composition the same id A_ID.
I want to join table M with table D in a one to many relationship using a join-table.
The following are some pseudo-java code converted from XML ORM mappings. So please excuse any mistakes written here. The mappings in the final code work so the typos here are not to blame.
#Entity()
public class M {
#EmbeddedId()
private EmbeddedMId id;
#OneToMany(name="d", #JoinTable(name="M-D",
joinColumns={
#JoinColumn(name="M_ID", referencedColumnName="M_ID"),
#JoinColumn(name="A_ID", referencedColumnName="A_ID", table="M")
},
inverseJoinColumns={
#InverseJoinColumn(name="D_ID", referencedColumnName="D_ID"),
#InverseJoinColumn(name="A_ID", referencedColumnName="A_ID", table="D", insertable="false", updatable="false")
}
))
private Set<D> dSet;
}
#Embeddable()
public class EmbeddedMId {
#Basic() private String A_ID;
#Basic() private String M_ID;
}
#Embeddable()
public class EmbeddedDId {
#Basic() private String A_ID;
#Basic() private String D_ID;
}
As you can see, the embeddables both use A_ID therefore we tried to make the 2nd A_ID in the join-table be readonly. The application starts and the mappings seem to be okay.
The problem is whenever I want to insert a new D object in the M entity, hibernate throws an SQL error invalid column index because while the prepared statement is correct as seen bellow, hibernate only provides the first 2 parameters instead of all three. (Values provided by hibernate are (VALID_M_ID, VALID_A_ID) instead of providing 3 values)
INSERT INTO M_D("M_ID", "A_ID", "D_ID") VALUES (?, ?, ?)
If I rename the 2nd inverseJoinColumn to have a new column name and make it insertable/updatable, the problem is solved. But this means that the A_ID is duplicated in both A_ID and A_REPEAT_ID column and this is what I want to avoid.
#InverseJoinColumn(name="A_REPEAT_ID", referencedColumnName="A_ID", table="D")
Is there a way to tell Hibernate that my EmbeddedDId needs to be mapped over the D_ID and A_ID (readonly) correctly when doing the insertions?
I hope my explanation is clear enough, but feel free to ask for any clarifications.
Hibernate version is 5.2.17-FINAL
EDIT
The only other entity that is important in this case is pretty simple. But as requested I'll write it here
#Entity()
public class D {
#EmbeddedId()
private EmbeddedDId id;
/* other basic fields here */
}
I don't think insertable = false, updatable = false does what you want here. If you want the target column A_ID on D to be readonly, then you will have to map the column in the target entity D and specify there that the column is insertable = false, updatable = false but not on this association.

JPA One To One relationship between two columns (not PK and not FK)

I have two tables, one named "Category" and the other "Rule" that are related logically with One to One relationship using a field (code) different than the Primary Key (PK) of table and not phisically managed with a Foreign Key (FK):
CATEGORY
ID (PK) NUMBER
COD_RULE VARCHAR
NAME VARCHAR
.....
RULE
ID (PK) NUMBER
CODE VARCHAR
TYPE VARCHAR
.....
I haven't on Rule table FK to category ID but only unique constraint (the relation is 1 to 1)
Implemented in this way in JPA
public Category implement Serializable {
#Id
#Column (name="ID")
private Long id;
#NotNull
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="CODE_RULE" referencedColumnName="CODE", nullable=false)
private Rule;
#Column (name="NAME")
private String name;
//Getter and Setter methods
.......
}
public Rule implement Serializable {
#Id
#Column (name="ID")
private Long id;
#NotNull
#Column (name="CODE")
private String code;
#Column (name="TYPE")
private String type;
//Getter and Setter methods
.......
}
I need to:
When retrieve Categories obtain also all informations of associated Rule
SELECT c.*, r.type FROM Category c LEFT OUTER JOIN Rule r WHERE c.CODE_RULE = r.CODE
When edit Category maintain aligned CODE_RULE with CODE, so If I change CODE_RULE I would yo change automatically the CODE on Rule
UPDATE Category SET COD_RULE='5A', NAME='Test' WHERE ID=1
UPDATE Rule SET CODE='5A' WHERE CODE='AB'
I see on the specification that:
There are three cases for one-to-one associations: either the
associated entities share the same primary keys values, a foreign key
is held by one of the entities (note that this FK column in the
database should be constrained unique to simulate one-to-one
multiplicity), or a association table is used to store the link
between the 2 entities (a unique constraint has to be defined on each
fk to ensure the one to one multiplicity).
But with this implementation satisfy point 1. But not the point 2.
Suppose that I've already created Category (on ID = 1) and associated rule, when I edit category (having CODE_RULE = CODE = "AB") and change the code to "5A":
#PersistentContext
private EntityManager em;
.......
Category cat = em.find(Category.class, 1L);
cat.setName("Test");
cat.getRule().setCode("5A");
em.merge(cat);
I see that the code has been updated on Rule but not in Category:
BEFORE EDIT
Category (ID, COD_RULE, NAME) --> (1, AB, First Category)
Rule (ID, CODE, TYPE) --> (10, AB, C)
AFTER EDIT
Category (ID, COD_RULE, NAME)--> (1, AB, Test)
Rule (ID, CODE, TYPE) --> (10, 5A, C)
How can I do this work in JPA?
Is this type of operation supported in the JPA specification?
Is there an alternative (i.e. I have to merge before Rule and then Category)?
From your datamodel, it looks more like a Many To One relationship between Category and Rule, given in your data model only restrict each Category can refer to [0..1] Rule, but not restricting how many Categories that a Rule can be referred by.
Based on your comment, it seems that you can change the data model. Normally if it is a ~ToOne relationship, you should have the referring side referring as FK, which looks like this:
(Tables)
CATEGORY (
CATEGORY_ID NUMBER PK,
CATEGORY_CODE VARCHAR, // Unqiue
RULE_ID NUMBER FK to RULE,
... (Don't refer by RULE_CODE!!)
)
RULE (
RULE_ID NUMBER PK,
RULE_CODE VARCHAR, // unique, can be updated
...
)
Entity should look like
class Category {
#Id #Column(name="CATEGORY_ID)
Long id;
#ManyToOne // or #OneToOne if you really insist
#JoinColumn(name="RULE_ID)
Rule rule;
)
(class Rule is straight-forward, I will skip)
The HQL you mentioned should be
// When retrieving Category together with Rule
from Category c join fetch c.rule
for Point 2, as you mentioned in comment, you are trying to align Rule's code with Category's code, when Category's code is updated. This should be implemented as:
class Category {
//.....
public void setCode(String code) {
this.code = code;
this.rule.setCode(code);
}
//....
)
Base on personal experience, when using JPA, life will be much easier to drive data model base on domain model design. It should save a lot of problem caused by "data-model that looks tolerable".

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.

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.

How to map a 2-d matrix in Java to Hibernate/JPA?

I have a legacy database I'm trying to redesign into the 21st century. One of the existing data structures involves a particular class which contains a 2-dimensional matrix of values. If I were to reverse-engineer this class from the database, I'd end up with a series of attributes like:
private BigDecimal NODE_1_MATRIX_POS_1_1;
private BigDecimal NODE_1_MATRIX_POS_1_2;
and so on. Since this is a 6x6 matrix, there are a lot of such columns.
I've been looking for a better way, but I'm not sure I'm there. What I'd like to do is something like this:
#Entity
public class TestClass {
#Id
private long id;
#CollectionOfElements
#JoinTable(
name="MATRIX_DATA",
joinColumns=#JoinColumn(name="ENTRY_ID"))
private List<List<BigDecimal>> matrix;
But this fails:
org.hibernate.MappingException: Could not determine type for: java.util.List, at table: MATRIX_DATA, for columns: [org.hibernate.mapping.Column(element)]
Rather than just trying to fix the error, I thought I'd ask around and try to find the right approach to solving this mapping challenge. Has anyone found success and satisfaction mapping multidimensional arrays via JPA?
Rather than just trying to fix the error, I thought I'd ask around and try to find the right approach to solving this mapping challenge. Has anyone found success and satisfaction mapping multidimensional arrays via JPA?
AFAIK, nested collections are not supported by standard JPA. The JPA wiki book has a good section on this topic (I'm quoting only a part of it):
Nested Collections, Maps and Matrices
It is somewhat common in an object
model to have complex collection
relationships such as a List of
Lists (i.e. a matrix), or a Map of
Maps, or a Map of Lists, and so
on. Unfortunately these types of
collections map very poorly to a
relational database.
JPA does not support nested collection relationships, and normally
it is best to change your object model
to avoid them to make persistence and
querying easier. One solution is to
create an object that wraps the nested
collection.
For example if an Employee had a
Map of Projects keyed by a
String project-type and the value a
List or Projects. To map this a
new ProjectType class could be
created to store the project-type and
a OneToMany to Project.
...
And that would be my suggestion. For example:
#Entity
public class TestClass {
#Id
private long id;
#OneToMany(mappedBy="testClass")
private List<MatrixRow> matrix;
}
Where MatrixLine would be (omitting many details):
#Entity
public class MatrixRow {
#Id
private long id;
#ManyToOne
private TestClass testClass;
#CollectionOfElements
private List<BigDecimal> row;
}
Or maybe you could use a custom user type (I'm not too sure how this would work).
Or (after all, you're already using non portable annotations) have a look at this question to see how you could extend Hibernate:
How do I map a nested collection, Map<Key,List<Values>>, with hibernate JPA annotations?
Hibernate Types project
You can map a PostgreSQL multidimensional array using the Hibernate Types project.
You can choose to use a Java array on the entity attribute side or use List.
Database table
For exmaple, assuming you have the following plane database table:
CREATE TABLE plane (
id INT8 NOT NULL,
name VARCHAR(255),
seat_grid seat_status[][],
PRIMARY KEY (id)
)
Where the seat_status is a PostgreSQL enum:
CREATE TYPE seat_status
AS ENUM (
'UNRESERVED',
'RESERVED',
'BLOCKED'
);
JPA entity
You can map the seatGrid column using the EnumArrayType:
#Entity(name = "Plane")
#Table(name = "plane")
#TypeDef(
name = "seat_status_array",
typeClass = EnumArrayType.class
)
public static class Plane {
#Id
private Long id;
private String name;
#Type(
type = "seat_status_array",
parameters = #org.hibernate.annotations.Parameter(
name = "sql_array_type",
value = "seat_status"
)
)
#Column(
name = "seat_grid",
columnDefinition = "seat_status[][]"
)
private SeatStatus[][] seatGrid;
//Getters and setters omitted for brevity
public SeatStatus getSeatStatus(int row, char letter) {
return seatGrid[row - 1][letter - 65];
}
}
So, you need to declare the appropriate Hibernate Type to use. For enums, you need to use the EnumArrayType:
#TypeDef(
name = "seat_status_array",
typeClass = EnumArrayType.class
)
The #Type annotation allows you to pass parameters to the Hibernate Type, like the SQL array class:
#Type(
type = "seat_status_array",
parameters = #org.hibernate.annotations.Parameter(
name = "sql_array_type",
value = "seat_status"
)
)
Testing time
Now, when you persist the following Post entity:
entityManager.persist(
new Plane()
.setId(1L)
.setName("ATR-42")
.setSeatGrid(
new SeatStatus[][] {
{
SeatStatus.BLOCKED, SeatStatus.BLOCKED,
SeatStatus.BLOCKED, SeatStatus.BLOCKED
},
{
SeatStatus.UNRESERVED, SeatStatus.UNRESERVED,
SeatStatus.RESERVED, SeatStatus.UNRESERVED
},
{
SeatStatus.RESERVED, SeatStatus.RESERVED,
SeatStatus.RESERVED, SeatStatus.RESERVED
}
}
)
);
Hibernate will issue the proper SQL INSERT statement:
INSERT INTO plane (
name,
seat_grid,
id
)
VALUES (
'ATR-42',
{
{"BLOCKED", "BLOCKED", "BLOCKED", "BLOCKED"},
{"UNRESERVED", "UNRESERVED", "RESERVED", "UNRESERVED"},
{"RESERVED", "RESERVED", "RESERVED", "RESERVED"}
},
1
)
And, when fetching the entity, everything works as expected:
Plane plane = entityManager.find(Plane.class, 1L);
assertEquals("ATR-42", plane.getName());
assertEquals(SeatStatus.BLOCKED, plane.getSeatStatus(1, 'A'));
assertEquals(SeatStatus.BLOCKED, plane.getSeatStatus(1, 'B'));
assertEquals(SeatStatus.BLOCKED, plane.getSeatStatus(1, 'C'));
assertEquals(SeatStatus.BLOCKED, plane.getSeatStatus(1, 'D'));
assertEquals(SeatStatus.UNRESERVED, plane.getSeatStatus(2, 'A'));
assertEquals(SeatStatus.UNRESERVED, plane.getSeatStatus(2, 'B'));
assertEquals(SeatStatus.RESERVED, plane.getSeatStatus(2, 'C'));
assertEquals(SeatStatus.UNRESERVED, plane.getSeatStatus(2, 'D'));

Categories