I searched on Google and StackOverflow but was unable to find a situation that appears to match mine.
I am using MySQL 5.5 and Java 7 JPA to interface with a database from within Java programs. I have a few tables with a many-to-many relationship and have corresponding join tables defined. I successfully tested adding and removing entries to a join table, but ran into a problem when re-running the "add" test. First, let me provide some of the code for reference:
Member.java:
#Entity
#Table(name = "member")
public class Member implements Serializable {
#Id
#TableGenerator(name = "member_seq_gen", table = "sequence", pkColumnName = "seq_name", valueColumnName = "seq_count", pkColumnValue = "member_seq")
#GeneratedValue(strategy = GenerationType.TABLE, generator = "member_seq_gen")
private long id;
... other fields not shown ...
#ManyToMany
#JoinTable(name = "j_member_strength", joinColumns = { #JoinColumn(name = "member_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "program_id", referencedColumnName = "id") })
private Collection<StrengthProgram> programs;
...
public void register(StrengthProgram program) {
programs.add(program);
}
public void unregister(StrengthProgram program) {
programs.remove(program);
}
}
StrengthProgram.java:
#Entity
#Table(name = "strength_program")
public class StrengthProgram implements Serializable {
#Id
#TableGenerator(name = "strength_program_seq_gen", table = "sequence", pkColumnName = "seq_name", valueColumnName = "seq_count", pkColumnValue = "strength_program_seq")
#GeneratedValue(strategy = GenerationType.TABLE, generator = "strength_program_seq_gen")
private long id;
... other fields not shown ...
#ManyToMany(mappedBy = "programs")
private Collection<Member> members;
...
}
SQL for the join table:
DROP TABLE IF EXISTS `cf531alt`.`j_member_strength` ;
CREATE TABLE IF NOT EXISTS `cf531alt`.`j_member_strength` (
`member_id` INT(11) NOT NULL ,
`program_id` INT(11) NOT NULL ,
PRIMARY KEY (`member_id`, `program_id`) ,
CONSTRAINT `fk_member_has_strength_program_member1`
FOREIGN KEY (`member_id` )
REFERENCES `cf531alt`.`member` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_member_has_strength_program_strength_program1`
FOREIGN KEY (`program_id` )
REFERENCES `cf531alt`.`strength_program` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;
CREATE INDEX `fk_member_has_strength_program_strength_program1_idx` ON `cf531alt`.`j_member_strength` (`program_id` ASC) ;
CREATE INDEX `fk_member_has_strength_program_member1_idx` ON `cf531alt`.`j_member_strength` (`member_id` ASC) ;
I have logic elsewhere that will "register" or "un-register" a member with a strength program by adding or removing the program object to/from the programs collection in the Member object.
I was able to register a collection of members with a program and un-register a subset of those members successfully, but when I went to re-run the register test with the original list of members, I got a duplicate entry exception. I understand why this is happening. What I don't know is how to prevent it or handle the situation if/when it arises. Following is an excerpt of the exception:
[EL Warning]: 2014-12-29 19:22:25.075--ClientSession(1337625898)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '183-1' for key 'PRIMARY'
Error Code: 1062
Call: INSERT INTO j_member_strength (program_id, member_id) VALUES (?, ?)
bind => [2 parameters bound]
Query: DataModifyQuery(name="programs" sql="INSERT INTO j_member_strength (program_id, member_id) VALUES (?, ?)")
...
I'm not very familiar with how JPA handles one-to-many or many-to-many relationships in the Java code. I wasn't sure if the relationship would be "created" using the method above until I tried it. I ran the test and then queried the database and to my surprise (and satisfaction) found that entries had been "automatically" made in the join table.
Does anyone have ideas about how to avoid or mitigate the "duplicate entry" problem? If I have not been clear enough or if more code is needed, please let me know.
UPDATE:
Do I need to query the join table to see if an entry exists for the given member/program combination and take appropriate action based upon the result? This would be my first thought, but wasn't sure how much JPA did behind the scenes.
there is some tips in the code that I think maybe cause a problem:
you don't need register(program) and unregister(program) methods, you can directly use member.programs.add(program) or member.programs.remove(program).
in many-to-many association the entities are adding to both side I mean: program.members.add(member); member.programs.add(program) after that you can persist the member or program
Related
UPDATE: Changed code to be coherent to db model and changed details of the model itself. Also added code of the section that causes the error.
I am trying to implement a small database consulting system in Hibernate with PostgreSQL and having issues with one specific pair of tables. As you can see, it's a system for car rental services, and the tables store drivers and rentals. A driver is supposed to be able to have multiple rentals (but not the other way around).
Problem Tables
CREATE TABLE Driver(
cod_driver SERIAL PRIMARY KEY,
cod_client INTEGER,
num_license BIGINT UNIQUE,
expiration_license DATE,
ident_driver BIGINT,
FOREIGN KEY (cod_client)
REFERENCES Client(cod_client)
ON UPDATE CASCADE
ON DELETE CASCADE
);
CREATE TABLE Rental(
cod_rental SERIAL PRIMARY KEY,
cod_plate VARCHAR(10),
cod_dest VARCHAR(10),
cod_driver INTEGER,
date_delivery DATE,
FOREIGN KEY (cod_plate)
REFERENCES Vehicle(cod_plate)
ON UPDATE CASCADE
ON DELETE NO ACTION,
FOREIGN KEY (cod_dest)
REFERENCES Location(cod_location)
ON UPDATE CASCADE
ON DELETE NO ACTION,
FOREIGN KEY (cod_driver)
REFERENCES Driver(cod_driver)
ON UPDATE CASCADE
ON DELETE CASCADE
);
I did my implementation using Hibernate as follows (short of getters/setters for brevity):
Driver
#Entity
public class Driver {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer cod_driver;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cod_client")
private Client client;
private Long num_license;
private Long ident_driver;
private LocalDate expiration_license;
}
Rental
#Entity
public class Rental {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer cod_rental;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cod_plate")
private Vehicle vehicle;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cod_dest")
private Location location_dest;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cod_driver")
private Driver driver;
private LocalDate date_delivery;
Context of PSQLException
Persist function call in Main.java (clientGet is obtained through successfull queries, and inserts is just a class for queries):
Driver d = new Driver(clientGet, 3294324792L, 321312931L, LocalDate.of(2030, 10, 01));
inserts.insertEntity(d);
insertEntity function:
public void insertEntity(Object o) // Basic insertion of any persistent object
{
em.getTransaction().begin();
em.persist(o);
em.getTransaction().commit();
}
Error
The error I get is this:
Caused by: org.postgresql.util.PSQLException:
ERROR: insert or update on table "driver" violates foreign key
constraint "fkdfq0qhvpkw1dqguk6dv1dsj0t"
Detail: Key (cod_driver)=(3) is not present in table "rental".
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2497)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2233)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:310)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:446)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:370)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:149)
at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:124)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175)
What I've Considered
From my understanding, the relationship didn't need to be bidirectional (though I did try to use mappedBy), given that I don't store rentals in driver table.
I just don't understand what constraint could possibly be violated by this. It's as if it expects the value of cod_driver to be already in the rentals table, but a rental entity depends on the pre-existence of the driver existence. SQL for the database doesn't seem to have any constraint like that.
Can someone help me understand what I'm doing wrong? I tried all things I found, but nothing shed any light on this.
use many to many relationship between rental and driver.the same problem i faced once in my PurchaseOrder and PaymentMethods relationship.
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "rental_driver", joinColumns = #JoinColumn(name = "rental_id"), inverseJoinColumns = #JoinColumn(name = "driver_id"))
private List driver = new List();
I have this Parent class
#Entity
#Table(name = "category")
#NamedQuery(name = "category.findAll", query = "SELECT c FROM Category c")
public class Category implements Serializable {
public Category(){}
#Column(name = "name", nullable = false)
#Id
private String name;
#Column(name = "col2")
private Boolean col2;
}
And i have referenced the parent table in child table as follows:
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "cat_name")
private Category category
when i run this JPQL query
update Category c SET c.name=:newName ,c.termsCanHaveChildren=:canHaveChdrn where c.name=:oldName
it's return with foreign key constraint error while i have put Cascade All in child field
Cannot delete or update a parent row: a foreign key constraint fails (`terms`.`term`, CONSTRAINT `FKaykenypxci167nqioh4xx9p3a` FOREIGN KEY (`cat_name`) REFERENCES `category` (`name`))
The problem lays at the constraint being generated by your persistence provider (hibernate), for the #JoinColumn(name = "cat_name") at the child table (and not with the CascadeType that you're defining)...
The generated constraint should indicated that when the PK of Category is Updated, any reference to such column should be updated also...
I believe this configuration should work (but you need to test it first, because I always generated my database model using scripts and not using hibernate features):
#ManyToOne
#JoinColumn(
name = "cat_name",
foreignKey = #ForeingKey(
name = "fk_child_category",
foreignKeyDefinition = "FOREIGN KEY (cat_name) REFERENCES category ON UPDATE CASCADE"
)
)
private Category category;
Also you need to check if your database supports "ON UPDATE CASCADE"... According to this link, oracle does not... (What database are you using?)
If this does not work, try the suggestion of Michelle...
That's expected: you are changing the Primary Key (#Id), that's used in a Foreign Key (#JoinColumn).
Use a surrogated immutable primary key.
Here is an exception I'm getting:
[EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd):
org.eclipse.persistence.exceptions.DatabaseException Internal Exception:
java.sql.SQLSyntaxErrorException: Vergleiche zwischen 'BIGINT' und 'VARCHAR (UCS_BASIC)'
werden nicht unterstützt.
Error Code: 30000
Call: SELECT t1.ID, t1.TEXTINFO FROM COORDINATESLOCATION_INFORMATION t0, TEXTINFORMATION t1
WHERE ((t0.CoordinatesLocation_ID = ?) AND (t1.ID = t0.informationList_ID))
bind => [1 parameter bound]
When I run my application first (without an empty database) everything works. I easily can manage data in all CRUD functions. Later (after an unspecific time or several requests), the exception appears.
This is a tourist information application. There are authors that create tours. One tour contains many locations. One location contains many information. The associations are realised as compositions.
As I changed every association to eager fetch, the exception appears earlier in the workflow.
Here are the code snippets:
#Entity
public class CoordinatesLocation implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String description;
#OneToMany(fetch = FetchType.EAGER)
private List<Information> informationList = new ArrayList<>();
private double lat;
private double lng;
}
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Information implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
#Entity
public class TextInformation extends Information {
private String textInfo;
}
Plus getter & setter!
Thanks for helping!
With the code you have provided, I can recreate the issue in EclipseLink . The generated script is;
CREATE TABLE COORDINATESLOCATION_INFORMATION (CoordinatesLocation_ID BIGINT
NOT NULL, informationList_ID VARCHAR(255) NOT NULL, PRIMARY KEY
(CoordinatesLocation_ID, informationList_ID))
I have tried explicitly defining the columns of the join table;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable (name = "COORDINATESLOCATION_INFORMATION",
joinColumns = #JoinColumn(name = "CoordinatesLocation_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "Information_ID",referencedColumnName = "ID" ))
private List<Information> informationList;
….but still get VARCHAR(255).
I have removed the
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
From the Information class (so defaulting to SINGLE_TABLE strategy) and this leads to the desired script:
CREATE TABLE COORDINATESLOCATION_INFORMATION (CoordinatesLocation_ID BIGINT NOT
NULL, informationList_ID BIGINT NOT NULL, PRIMARY KEY (CoordinatesLocation_ID,
informationList_ID)).
So, following that somewhat empirical analysis (I’m not sure why ID was mapped to type VARCHAR(255)) you have a couple of choices.
Modify you CREATE TABLE COORDINATESLOCATION_INFORMATION script to include informationList_ID BIGINT.
Change to #Inheritance(strategy = InheritanceType. SINGLE_TABLE). This requires the addition of a discriminator column and may not be in line with your approach.
Note also that with #OneToMany you do not need a join table if you add a Foreign Key in your Information table and define #ManyToOne on the Information side and #OneToMany on the CoordinatesLocation side with the mappedBy Attribute.
This link will give some idea of that. https://en.wikibooks.org/wiki/Java_Persistence
I can't read the localized message in your exception, however I'll guess that it says you can't compare bigint and varchar. I'll further guess that the error is in this part of the SQL: (t1.ID = t0.informationList_ID). Is one of those columns a bigint and the other a varchar?
basically here t0.CoordinatesLocation_ID = ? at sometime your parameter is of type BIGINT and the reqested type is VARCHAR, you should look at this
Inspection Entity:
#Entity
#Table(name="INSPECTION")
public class Inspection implements Serializable
{
...
#OneToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, orphanRemoval=true)
#OrderColumn(name="LIST_INDEX", nullable=false)
#JoinColumn(name="INSPECTION_ID")
private List<RecommendationInstance> recommendations;
...
}
RecommendationInstance Entity
#Entity
#Table(name = "RECOMMENDATION_INSTANCE")
public class RecommendationInstance implements Serializable
{
#SequenceGenerator(name="RECOMMENDATION_INST_SEQ_GEN", sequenceName="RECOMMENDATION_INST_SEQ", allocationSize=1, initialValue=100)
#Id #GeneratedValue(generator="RECOMMENDATION_INST_SEQ_GEN", strategy=GenerationType.SEQUENCE)
private Long id;
#Column(name="INSPECTION_ID")
private Long inspectionId;
#ManyToOne
#JoinColumn(name="RECOMMENDATION_ID")
private Recommendation recommendation;
#Column(name="DESCRIPTION")
private String description;
...
}
And the table is created as follows:
CREATE TABLE "RECOMMENDATION_INSTANCE"
( "ID" NUMBER(19,0) NOT NULL,
"INSPECTION_ID" NUMBER(19,0) NOT NULL,
"RECOMMENDATION_ID" NUMBER(19,0) NOT NULL,
"DESCRIPTION" VARCHAR2(4000 BYTE) NOT NULL,
"LIST_INDEX" NUMBER(4,0) NOT NULL
) ;
When a new RecommendationInstance is created and I attempt to save the InspectionEntity I get the following error:
Caused by: org.eclipse.persistence.exceptions.DatabaseException:
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: integrity constraint violation: NOT NULL check constraint; SYS_CT_10161 table: "RECOMMENDATION_INSTANCE" column: "LIST_INDEX"
Error Code: -10
Call: INSERT INTO RECOMMENDATION_INSTANCE (ID, DESCRIPTION, INSPECTION_ID, RECOMMENDATION_ID) VALUES (?, ?, ?, ?)
bind => [102, Sprinkler System DESCRIPTION, 110, 40]
Am I missing some relationship here? It looks as though the list_index is being ignored completely.
To give further information, if needed, I did have this working using a join table. However I am doing a refactor since the join table is not needed. This moved the LIST_INDEX column from the join table to the RecommendationInstance table.
I have done this before but using the #OrderBy annotation, for instance, an piece of code I wrote recently:
#OneToMany(mappedBy = "product")
#OrderBy("createdDateTime ASC")
private Collection<SkuUpc> skuUpcs;
Where SkuUpc has a fied
#Column(name = "created_dt")
private Date createdDateTime = new Timestamp(new Date().getTime());
I found that when I removed the NOT NULL constraint then everything worked (duh), but I decided I can deal with that for now. Looking at the logs, JPA first inserts the row without the list_index (thus the constraint violation) then immediately after runs an update to set the list_index.
This answer really creates a more specific question as to why it doesn't set the list_index upon insertion of the row, even when I specify nullable=false
I asked the more specific question here: Why does JPA update the OrderColumn instead of setting it on creation?
I have a situation that is quite similar to the one outlined in this question's diagram: JPA. JoinTable and two JoinColumns, although with different issues.
I have three tables: Function, Group, and Location. Currently, I have a join table set up between Location and Group using #JoinTable. It is #ManyToMany on both sides, and works perfectly fine.
I am attempting to add the constraint that no Location should be associated with more than one Group that has the same Function. So I added a column for Function to my join table in my SQL schema and a uniqueness constraint across the Location and Function columns, like so:
create table function_table (
id varchar(50),
primary key(id)
);
create table group_table (
id varchar(50),
function_id varchar(50) not null,
primary key(id)
);
alter table group_table add constraint FK_TO_FUNCTION foreign key (function_id) references function_table;
create table location_table (
id varchar(50),
primary key(id)
);
create table group_location_join (
location_id varchar(50) not null,
group_id varchar(50) not null,
function_id varchar(50) not null,
primary key(location_id, group_id, function_id),
unique(location_id, function_id)
);
alter table group_location_join add constraint FK_TO_LOCATION foreign key (location_id) references location_table;
alter table group_location_join add constraint FK_TO_GROUP foreign key (group_id) references group_table;
alter table group_location_join add constraint FK_TO_FUNCTION foreign key (function_id) references function_table;
I then attempted to set up the following in my model entities:
#Entity
#Table(name = "function_table")
public class Function {
#Id
#Column(name = "id", length = 50)
private String id;
}
#Entity
#Table(name = "group_table")
public class Group {
#Id
#Column(name = "id", length = 50)
private String id;
#ManyToOne
#JoinColumn(name = "function_id", referencedColumnName = "id", nullable = false)
private Function function;
#ManyToMany
#JoinTable(name = "group_location_join",
joinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id"),
#JoinColumn(name = "function_id", referencedColumnName = "function_id")},
inverseJoinColumns = #JoinColumn(name="location_id", referencedColumnName = "id"))
private Set<Location> locations;
}
#Entity
#Table(name = "location_table")
public class Location {
#Id
#Column(name = "id", length = 50)
private String id;
#ManyToMany
#JoinTable(name = "group_location_join",
joinColumns = #JoinColumn(name="location_id", referencedColumnName = "id")
inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id"),
#JoinColumn(name = "function_id", referencedColumnName = "function_id")})
private Set<Group> groups;
}
(Obviously, there is more to these entities, but I stripped them down to only the parts relevant to this question.)
This does not work. When I write a simple test to create a Location associated with a Group that is associated with a Function, the minute I try to flush the session to commit the transaction, Hibernate gives me this:
java.lang.ClassCastException: my.package.Group cannot be cast to java.io.Serializable
I think what's happening is that Hibernate is getting confused, throwing up its hands, and saying "I'll just serialize it, send it to the database, and hope it knows what's going on."
When I add implements Serializable and add a serialVersionUID to Group, I then get this:
org.hibernate.exception.SQLGrammarException: user lacks privilege or object not found: FUNCTION_ID
I'm not really sure how to proceed at this point, or if perhaps I have already proceeded too far down the wrong path. Maybe I'm not thinking about the SQL correctly, and there is a much easier way to ensure this constraint that doesn't involve all this ridiculousness.
Edit: In my system, the DAOs for the tables involved have no save capabilities. Which means that as long as my constraint is set up in the database, my application doesn't care; it can't insert things that violate the constraint because it can't insert things at all.
Edit 2: I never originally solved the stated problem, and instead simply added a third column in my database schema without touching the Java code, as stated in my first Edit section above. But I have since experimented with creating an explicit join table object with an #Embedded compound key, and it seems to work.
You are trying to create a composite primary key. In Hibernate you can do it using the #Embeddable annotation. In the example below you can find the way to use a composite key for two entities.
I believe you can move forward with this example and create your own version of primary key.
Mapping ManyToMany with composite Primary key and Annotation: