JPA: set default value when remove relationship - java

I have a self joined entity Authority joined by parent id, I modify the entire family tree just by modify the root, creating and modifying a tree node works well, but when I delete a node, jpa set the entity's parent id to null, in consideration of the database performance, I use not null in every columns, as a result, it brings an MySQLIntegrityConstraintViolationException: Column 'parent_id' cannot be null. The SQL is update t_authority set parent_id=null where parent_id=?.
So, my question is, why the default value 0L I set to parentId not works? What should I do to make it use the default value in parentId when removing the relationship.
#Entity
#Table(name = "t_authority")
#SQLDelete(sql =
"UPDATE t_authority " +
"SET status = " + DB_STATUS_NOT_EXISTS +
" WHERE id = ?")
#Where(clause = "status = " + DB_STATUS_EXISTS)
public class Authority implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#Column(name = "authority_name")
private String authorityName;
#Column(name = "authority_Id")
private String authorityId;
#Column(name = "parent_id")
private Long parentId = 0L;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "parent_id")
#Where(clause = "status = " + DB_STATUS_EXISTS)
private Set<Authority> childAuthorities;
mysql table
create table `t_authority` (
`id` bigint unsigned not null primary key auto_increment,
`authority_name` varchar(100) not null default '' comment 'authority name',
`authority_id` varchar(100) not null default '' comment 'authority id',
`parent_id` bigint unsigned not null default 0 comment 'parent id'
) engine=innodb default charset=utf8 collate=utf8_general_ci;

Related

JPA allocation Size causing Auto Generated Sequence to Reused after AllocationSize

I have recursive parent and child relation for Graph. When I have a large Graph with 50 or more nodes on a Single hibernate session, I get an error message "A different object with the same identifier value was already associated with the session". This is due to the fact the default allocation Size in Spring JPA is 50. I have overcome this error by setting allocationSize to 100 and increment by 100. But that does not solve the root of the problem. I can have any arbitrary # nodes in ONE session. I use saveAndFlush(NodeEntity) which throws this error message.
My question is How do I force Hibernate to fetch the primary key from DB after allocation size is limit is reached and be able to generate a new set primary key in a single session?
Hibernate version: hibernate-core-5.4.30.Final.jar
Error:
A different object with the same identifier value was already associated with the session : [graph.entity.NodeEntity#53]; nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [graph.entity.NodeEntity#53]
//GRAPH DATABASE.
CREATE TABLE IF NOT EXISTS node
(
id SERIAL NOT NULL,
name character varying(255) NOT NULL,
parent_id int,
CONSTRAINT node_pkey PRIMARY KEY (id),
CONSTRAINT node_parent_id_fk FOREIGN KEY (parent_id)
REFERENCES node (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
CREATE TABLE IF NOT EXISTS graph
(
id SERIAL NOT NULL,
name character varying(255) NOT NULL,
node_id int,
CONSTRAINT graph_pkey PRIMARY KEY (id),
CONSTRAINT graph_node_fk FOREIGN KEY (node_id)
REFERENCES node (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT graph_node_id_uk UNIQUE (node_id)
);
public class NodeEntity {
#Id
#SequenceGenerator(name = "node_id_seq", sequenceName = "node_id_seq",allocationSize = 50)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "node_id_seq")
#Column(name = "id")
int id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,orphanRemoval=true)
#JoinColumn(name = "parent_id")
private List<NodeEntity> children = new LinkedList<NodeEntity>();
}
public class GraphEntity{
#Id
#SequenceGenerator(name = "graph_id_seq", sequenceName = "graph_id_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "graph_id_seq")
#Column(name = "id")
int id;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "NODE_ID", unique = true, nullable = true, insertable = true, updatable = true)
private NodeEntity rootNode;
}
// Way to reproduce this.
void generate150deepChild(int count,NodeEntity node){
if(count == 100){
return
}else{
NodeEntity newNode = new NodeEntity("Child " +count)
node.getChildren().add(newNode);
cout++;
generate150deepChild(count,newNode);
}
}
NodeEntity rootNode = new NodeEntity("ROOT");
// PLEASE NOT if # NodeEntity < 50 everything works fine.
generate150deepChild(0,rootNode);
// PLEASE NOT all ids are zero so they are new node.
GraphEntity graph = new GraphEntity("TEST");
graph.setRootNode(rootNode);
graphRepository.saveAndFlush(graph);
// THIS WILL GENERATE Duplicate Primary key for NodeEntity.
Actual stack trace:
Caused by: javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [graph.entity.NodeEntity#131605]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:123)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:823)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:786)
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:261)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:499)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:423)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:532)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:463)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:426)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:153)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:459)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:247)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:175)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:104)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:813)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:786)
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:261)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:499)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:423)
Change to use GenerationType.SEQUENCE strategy and it should handle for you automatically :
#Id
#SequenceGenerator(name = "node_id_seq", sequenceName = "node_id_seq",allocationSize = 50)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "node_id_seq")
#Column(name = "id")
int id;
And you just need to make sure the followings:
Do not set the ID for each node manually.
The related sequence in DB (i.e. the sequence specified in sequenceName in #SequenceGenerator) is really configured and aligned with what you configure in #SequenceGenerator. In case of PostgreSQL , you can do :
alter sequence node_id_seq increment by 50;
Tip:
According to this, you can change to use the pooled or pooled-lo algorithm to reduce the DB roundtrip to get the ID by configuring the following settings:
<property name="hibernate.id.optimizer.pooled.preferred" value="pooled-lo" />

How to create UNIQUE column with JPA?

My POJO is using JPA and when I apply unique value in the column is not working for me I tried to use the UniqueConstraint and also not working for me .
below is my code
#Entity
#Table(name = "users", uniqueConstraints={#UniqueConstraint(columnNames = {"user_id"}),#UniqueConstraint(columnNames = {"username"}),#UniqueConstraint(columnNames = {"email"})})
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="user_id",unique=true,nullable = false)
private int UserId;
#Column(name = "username" ,unique=true,nullable = false)
private String Username;
#Column(name = "email",unique=true ,nullable = false)
private String email;
#Column(name = "firstname",nullable = false)
private String firstname;
#Column(name = "lastname", nullable = false)
private String lastname;
#Column(name = "password", nullable = false)
private String password;
#Column(name = "active")
private int active;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name="user_role", joinColumns=#JoinColumn(name="user_id"), inverseJoinColumns=#JoinColumn(name="role_id"))
private Set<Role> roles;
below is the generated table in the database (MySQL)
| users | CREATE TABLE users (
user_id int(11) NOT NULL,
username varchar(255) NOT NULL,
active int(11) DEFAULT NULL,
email varchar(255) NOT NULL,
firstname varchar(255) NOT NULL,
lastname varchar(255) NOT NULL,
password varchar(255) NOT NULL,
PRIMARY KEY (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
Hibernate log after spring start
Hibernate: create table users (user_id integer not null, username varchar(255) not null, active integer, email varchar(255) not null, firstname varchar(255) not null, lastname varchar(255) not null, password varchar(255) not null, primary key (user_id)) engine=InnoDB
Hibernate: alter table users drop index UKfnranlqhubvw04boopn028e6
Hibernate: alter table users add constraint UKfnranlqhubvw04boopn028e6 unique (username, email)
Hibernate: alter table users drop index UK_r43af9ap4edm43mmtq01oddj6
Hibernate: alter table users add constraint UK_r43af9ap4edm43mmtq01oddj6 unique (username)
Hibernate: alter table users drop index UK_6dotkott2kjsp8vw4d0m25fb7
Try like this:
#Entity
#Table(name="users",uniqueConstraints=#UniqueConstraint(columnNames={"user_id","username","email"}))
public class Users {
Apparently there's nothing wrong with this code. unique=true in #Column is a shortcut for #UniqueConstraint(columnNames = {"user_id"} and other particular constraints. You could only use one of them except for multiple unique constraints.
But if you are confused why the generated CREATE TABLE doesn't contain these unique constraints, check the log, you can find alter table commands just after CREATE TABLE.
In this case commands could be,
alter table users add constraint UK_some_id unique (username)
alter table users add constraint UK_some_id unique (email)
Hope this help.
I had the same problem here, set length to your unique columns.. It worked here. Do
#Column(name = "userName", length = 50, unique = true)
if you set unique in #Column
that's mean let your JPA provider create the database for you - it will create the unique constraint on the specified column.
But after creating a database, or you alter it once created,
then unique doesn't have any effect
in jpa the id is already unique so to make username and email unique add the statement
unique=true
to the corresponding #Column annotation.
PS : remember the unique cannot be null when insert new item you should drop the table before run the application and remove uniqueConstraints in table annotation
Set the column to have the fixed length make it work because mysql size for unique constrains limited to 1000 bytes
#Column(name = "username", length = 200, unique = true)
Use #javax.persistence.Column(unique = true).
Dont forget to restart your application.
&& you cant alter your column if there are still duplicated values in it.
drop your table, and recreate it with your generated ddl. (if ure not usin springboot)
&& if youre using springboot, go to your application.properties & set spring.jpa.hibernate.ddl-auto to update
This updates the schema if necessary.
The column needs to be non nullable for it to be unique!
#Column(unique = true) will NOT work
#Column(unique = true, nullable = false) will work
If the database or schema already exists before adding the uniqueConstraints then it won't work.
So drop the database or schema and run the application to create new database with these uniqueConstraints
DROP DATABASE database_name;

Spring JPA hibernate postgresql partion and timestamp without timezone

After searching a lot, I have a trouble to save timestamp data in PostgreSQL base using Spring JPA Hibernate.
Here my main table in PostgreSQL, it's a partitionning table :
CREATE TABLE public.archive_traffic_measure
(
measure_point_id integer NOT NULL,
measure_agregation_id integer NOT NULL,
measure_datetime timestamp without time zone NOT NULL,
measure_type_id integer NOT NULL,
any_flow integer,
f_any_flow smallint,
hgv_flow integer,
f_hgv_flow smallint,
occupation_rate smallint,
f_occupation_rate smallint,
average_speed smallint,
f_average_speed smallint,
CONSTRAINT pk_archive_traffic_measure PRIMARY KEY (measure_point_id, measure_agregation_id, measure_datetime, measure_type_id)
)
Here my main entity with spring JPA hibernate with #SQLInsert to suppress the check and don't have the problem with hibernate that no row is insert (because the row is insert in a child table) :
#Entity
#Table(name = "archive_traffic_measure")
#SQLInsert(sql = "INSERT INTO archive_traffic_measure (measure_point_id," +
"measure_agregation_id," +
"measure_datetime," +
"measure_type_id," +
"any_flow," +
"f_any_flow," +
"hgv_flow," +
"f_hgv_flow," +
"occupation_rate," +
"f_occupation_rate," +
"average_speed," +
"f_average_speed) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", check = ResultCheckStyle.NONE)
public class ArchiveTrafficMeasure implements Serializable {
#EmbeddedId
private ArchiveTrafficMeasureId id;
#Column(name = "any_flow")
private Integer anyFlow;
#Column(name = "f_any_flow")
private Integer F_AnyFlow;
#Column(name = "hgv_flow")
private Integer hgvFlow;
#Column(name = "f_hgv_flow")
private Integer F_hgvFlow;
#Column(name = "occupation_rate")
private Integer occupationRate;
#Column(name = "f_occupation_rate")
private Integer F_occupationRate;
#Column(name = "average_speed")
private Integer averageSpeed;
#Column(name = "f_average_speed")
private Integer F_averageSpeed;
}
And my id's entity with Spring JPA hibernate :
#Embeddable
#Table(name = "archive_traffic_measure")
public class ArchiveTrafficMeasureId implements Serializable {
#Column(name = "measure_point_id")
private int measurePointId;
#Column(name = "measure_agregation_id")
private int measureAgregationId;
#Column(name = "measure_datetime", columnDefinition = "timestamp without time zone")
#Temporal(TemporalType.TIMESTAMP)
private Date measureDateTime;
#Column(name = "measure_type_id")
private int measureType;
}
When I try to insert new data I have this error :
ERROR: column "measure_datetime" is of type timestamp without time zone but
expression is of type integer
The problem is the request be like :
INSERT INTO archive_traffic_measure VALUES (1,2,2018-04-18 17:00:00+01,1,40,null,null,null,null,null,null,null)
So the quote arount the timestamp is missing...
I have try to put the quote in the #SQLInsert "(?,?,'?',?,?,?,?,?,?,?,?,?)" but with that, I have the error :
The column's index is out of bound : 12, number of column : 11.
I have also try "(?,?,''?'',?,?,?,?,?,?,?,?,?)" with double quote
Or "(?,?,?::timestamp,?,?,?,?,?,?,?,?,?)" but nothing is working for far.
Can someone help me ? Thanks in advance.
First, add nullable = false for 'measure_datetime' column definition also remove composite key from #SQLInsert:
#SQLInsert(sql = "INSERT INTO archive_traffic_measure ("any_flow," +
"f_any_flow," +
"hgv_flow," +
"f_hgv_flow," +
"occupation_rate," +
"f_occupation_rate," +
"average_speed," +
"f_average_speed) VALUES (?,?,?,?,?,?,?,?,?)", check = ResultCheckStyle.NONE)
Then, when you persist ArchiveTrafficMeasure, you should set PK as ArchiveTrafficMeasureId and persist it.

Database schema not as per hibernate annotations

I am using hibernate to create entity. The attributes I used are as below :
#Id
#SequenceGenerator(name = "customer-id-gen", sequenceName = "CUSTOMERS_SEQ", allocationSize = 1)
#GeneratedValue(generator = "customer-id-gen", strategy = GenerationType.SEQUENCE)
#Column(name = "CUSTOMER_ID", length = 4, nullable = false)
private int customerId;
#Column(name = "CUSTOMER_NAME", length = 40, unique = false, nullable = false)
private String customerName;
#Column(name = "PHONE_NO", unique = true, nullable = true, length = 10)
private Long phoneNo;
However as i can see through logs that table created is as following structure :
create table CUSTOMER_ALL (
CUSTOMER_ID number(10,0) not null,
CUSTOMER_NAME varchar2(40 char) not null,
PHONE_NO number(19,0) unique,
primary key (CUSTOMER_ID)
)
I am not able to figure out how the phone_no attribute is converted into 19 size and customer_id to 10 ?
As per JPA, length only applies to String types.
Type 'int' controlled the storage size of CUSTOMER_ID.
Type 'Long' controlled the storage size of PHONE_NO.
Do you really want a phone number to be a Long? Better a String?

Incorrect SQL statements generated by JPA

I've encountered a strange error with JPA that is not persistence provider specific. I'm using JPA 2.0 and I'm using a generated schema.
In short: The generated schema includes a join table with three columns, but the generated insert statements treat this table as if it had only two columns.
Here are the mappings:
#Entity
#Table( name = "Game" )
public class MatchEntity implements Match, Serializable {
#Id
#GeneratedValue( strategy = GenerationType.AUTO )
private Long id;
#ManyToOne( targetEntity = ClubEntity.class )
private Club homeTeam;
#ManyToOne( targetEntity = ClubEntity.class )
private Club awayTeam;
#ManyToMany( targetEntity = PlayerEntity.class )
private Collection<Player> homeTeamPlayers;
#ManyToMany( targetEntity = PlayerEntity.class )
private Collection<Player> awayTeamPlayers;
private String location;
#Temporal( value = TemporalType.DATE )
#Column( name = "Match_Date" )
private Date date;
/* constructor, getters and setters follow */
}
#Entity
#Table( name = "Club" )
public class ClubEntity implements Club, Serializable {
#Id
#GeneratedValue( strategy = GenerationType.AUTO )
private Long id;
private String name;
#OneToMany( fetch = FetchType.EAGER, targetEntity = PlayerEntity.class,
mappedBy = "club" )
private Collection<Player> players = new ArrayList<Player>();
private String fieldName;
private Boolean archived;
/* constructor, getters and setters follow */
}
#Entity
#Table( name = "PLAYER" )
public class PlayerEntity implements Player, Serializable {
#Id
#GeneratedValue( strategy = GenerationType.AUTO )
private Long id;
private String firstName;
private String surname;
#Temporal( value = TemporalType.DATE )
private Date birthDate;
#Column( name = "pos" )
#Enumerated( EnumType.ORDINAL )
private Position position;
private Integer number;
private Boolean archived;
#ManyToOne( targetEntity = ClubEntity.class, fetch = FetchType.EAGER )
private Club club;
/* constructor, getters and setters follow */
}
From these mappings, the following schema gets created:
create table Club (id bigint generated by default as identity (start with 1), archived bit, fieldName varchar(255) not null, name varchar(255) not null, primary key (id))
create table Game (id bigint generated by default as identity (start with 1), Match_Date date, location varchar(255), awayTeam_id bigint, homeTeam_id bigint, primary key (id))
create table Game_PLAYER (Game_id bigint not null, homeTeamPlayers_id bigint not null, awayTeamPlayers_id bigint not null)
create table PLAYER (id bigint generated by default as identity (start with 1), archived bit, birthDate date, firstName varchar(255) not null, number integer, pos integer, surname varchar(255) not null, club_id bigint, primary key (id))
alter table Game add constraint FK21C0123B2A3B9E foreign key (homeTeam_id) references Club
alter table Game add constraint FK21C012F5972EAF foreign key (awayTeam_id) references Club
alter table Game_PLAYER add constraint FK267CF3AE6AE1D889 foreign key (Game_id) references Game
alter table Game_PLAYER add constraint FK267CF3AED51EDECF foreign key (homeTeamPlayers_id) references PLAYER
alter table Game_PLAYER add constraint FK267CF3AE6CBE869E foreign key (awayTeamPlayers_id) references PLAYER
alter table PLAYER add constraint FK8CD18EE13F2C6C64 foreign key (club_id) references Club
This line is important - this is the join table.
create table Game_PLAYER (Game_id bigint not null, homeTeamPlayers_id bigint not null, awayTeamPlayers_id bigint not null)
When I try to persist the Game entity (MatchEntity.java), this happens:
insert into Game_PLAYER (Game_id, awayTeamPlayers_id) values (?, ?)
Hibernate: insert into Game_PLAYER (Game_id, awayTeamPlayers_id) values (?, ?)
binding '2' to parameter: 1
binding '1' to parameter: 2
reusing prepared statement
insert into Game_PLAYER (Game_id, awayTeamPlayers_id) values (?, ?)
Hibernate: insert into Game_PLAYER (Game_id, awayTeamPlayers_id) values (?, ?)
binding '2' to parameter: 1
binding '2' to parameter: 2
done inserting collection: 2 rows inserted
Inserting collection: [football.model.entities.MatchEntity.homeTeamPlayers#2]
Executing batch size: 2
about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
Could not execute JDBC batch update [insert into Game_PLAYER (Game_id, awayTeamPlayers_id) values (?, ?)]
JPA tries to insert two rows to the join table, each affecting only two columns of the three.
What I have tried:
Getting rid of the interfaces in the mappings altogether
Defining an explicit join table
Using OpenJPA instead of Hibernate
Neither did resolve the problem.
edit: code for eager fetching:
#Transactional(readOnly = true)
public Collection<Match> findAll() {
em.createQuery("SELECT m FROM MatchEntity m "
+ "JOIN FETCH m.homeTeamPlayers", MatchEntity.class).getResultList();
List<MatchEntity> rList = em.createQuery("SELECT m FROM MatchEntity m "
+ "JOIN FETCH m.awayTeamPlayers", MatchEntity.class).getResultList();
Collection<Match> result = new ArrayList<Match>( rList );
return result;
}
Perhaps you need different join tables for homeTeamPlayers and awayTeamPlayers:
#ManyToMany( targetEntity = PlayerEntity.class )
#JoinTable(name = "Game_HomeTeamPlayers")
private Collection<Player> homeTeamPlayers;
#ManyToMany( targetEntity = PlayerEntity.class )
#JoinTable(name = "Game_AwayTeamPlayers")
private Collection<Player> awayTeamPlayers;

Categories

Resources