I'm having trouble using NATURAL JOIN in Hibernate with #Formula.
I have three tables:
CREATE TABLE IF NOT EXISTS `tz_points_logs` (
`points_log_id` bigint(20) NOT NULL,
`points_to_uid` bigint(20) NOT NULL,
`points_get_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`points_rule_id` int(4) NOT NULL,
`points_meta` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `tz_points_rules` (
`points_rule_id` int(4) NOT NULL,
`points_rule_credits` int(4) NOT NULL,
`points_rule_title` varchar(32) NOT NULL,
`points_rule_description` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `tz_users` (
`uid` bigint(20) NOT NULL,
`username` varchar(16) NOT NULL,
`password` varchar(32) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
In Hibernate, there're three mapping Models: User, PointsLog and PointsRule.
Now, there a #Formal field in User Model:
#Entity
#Table(name = "tz_users")
public class User implements Serializable {
// Getters and Setters
#Id
#GeneratedValue
#Column(name = "uid")
private long uid;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#OneToMany(targetEntity = PointsLog.class,
fetch = FetchType.LAZY, mappedBy = "user")
private List<PointsLog> pointsLogs = new ArrayList<PointsLog>();
#Formula(value = "(SELECT COUNT(*) FROM tz_points_logs pl NATURAL JOIN tz_points_rules WHERE pl.points_to_uid=uid)")
private long credit;
}
There's an error while get credit of the User:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use
near 'user0_.NATURAL JOIN tz_points_rules WHERE pl.points_to_uid=user0_.uid) as formul' at line 1
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:377)
at com.mysql.jdbc.Util.getInstance(Util.java:360)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:978)
...
at java.lang.Thread.run(Thread.java:745)
It seems that the Hibernate cannot recognize NATURAL JOIN in #Formula.
Is there any other method to implement this function?
BTW, following statement works fine.
#Formula(value = "(SELECT COUNT(*) FROM tz_points_logs pl WHERE pl.points_to_uid=uid)")
Thanks a lot.
Similar issue is discussed in this answer. The point here is - Hibernate treats the NATURAL keyword as a property/column name. A workaround should be - register NATURAL as a keyword in the dialect.
For example:
// extend the correct dialect ... in this case MySql
// but simply extend the currently used dialect
public class CustomMySQLDialect extends MySQL5InnoDBDialect
{
public CustomMySQLDialect()
{
super();
registerKeyword("NATURAL");
}
}
Next configure Hibernate to be using this CustomMySQLDialect, and the FORMULA should be generated:
// instead of this
// user0_.NATURAL JOIN
// we should get this
NATURAL JOIN
Related
I'm using this hibernate-types that allows hibernate to translate SQL layer data types into java classes in my springboot application, here I'm trying to add a text array field called user array.
#Entity
#Table(name = "user_update")
#Getter
#Setter
#NoArgsConstructor
#RequiredArgsConstructor
#TypeDef(name = "list-array", typeClass = ListArrayType.class)
public class UserUpdate {
#Id #NonNull private String userKey;
#Column #NonNull private String userName;
#Column #NonNull private Instant updatedAt;
#Type(type = "list-array")
#Column(columnDefinition = "text[]")
#NonNull
private List<String> userArray;
}
I can insert data into the table, but now I want to add a test and I see the following error message from the table.sql
CREATE TABLE IF NOT EXISTS user_update (
user_key VARCHAR(255) NOT NULL,
user_name VARCHAR(255) NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
user_array TEXT ARRAY NOT NULL,
PRIMARY KEY(org_key)
);
maybe it is because the test I'm running uses #DataJpaTest and for some reason it can not recognize the new text[] field ?
Error executing DDL "create table user_update (user_key varchar(255) not null, user_name varchar(255), user_array text[], updated_at timestamp, primary key (user_key))" via JDBC Statement
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "create table user_update (user_key varchar(255) not null, user_name varchar(255), user_array text[*][], updated_at timestamp, primary key (user_key))"; expected "(, ARRAY, INVISIBLE, VISIBLE, NOT, NULL, AS, DEFAULT, GENERATED, ON, NOT, NULL, DEFAULT, NULL_TO_DEFAULT, SEQUENCE, SELECTIVITY, COMMENT, CONSTRAINT, COMMENT, PRIMARY, UNIQUE, NOT, NULL, CHECK, REFERENCES, ,, )"; SQL statement:
With #DataJpaTest Spring will instruct Hibernate to create the schema and that will use the information that you provided in your annotations, but it seems this is not legal for H2.
Try using the following instead:
#Column(columnDefinition = "text array")
I have a products table that contains especially a product_name and a product_type.
For certain product types, I'd like to create some kind of overlay mapping table that replaces the value in product_name.
In pure mysql, I would solve this as follows:
CREATE TABLE products (
id int NOT NULL AUTO_INCREMENT,
product_type varchar(20) NOT NULL,
product_name varchar(255) NOT NULL,
product_price;
product_quantity;
...
)
#Entity
public class Product {
long id;
String product_type;
String product_name;
String product_price;
...
}
CREATE TABLE product_mapping (
id int NOT NULL AUTO_INCREMENT,
product_type varchar(20) NOT NULL,
product_name varchar(255) NOT NULL,
PRIMARY KEY (id)
);
My goal: if product_mapping contains the product_type, override the product_name. Else, stick to the value in products.product_name table.
SELECT ..., ifnull(product_mapping.product_name, products.product_name) AS product_name
FROM products
LEFT JOIN product_mapping ON products.product_type = product_mapping.product_type;
But how could I create the same mapping with a hibernate #Entity
Sidenote: A #Formula creates an additional SELECT for each query, whose result is merged into the #Entity. I'm looking for a JOIN!
I'm running integrity tests on a Spring Boot 2.1.4 project with JUnit 4.12 and I have run into a problem running a save (Comment) endpoint test in a bidirectional relationship of type #OneToOne - #ManyToOne when I use the #JsonManagedReference and #JsonBackReference annotations to avoid recursion.
The problem is that when I run the test it isn't converting the parent (Author class) of the relation to JSON, only the child (Comment class) is converted, but with none reference to parent.
Object of class Comment received to convert it to JSON
Result of convert the object of class Comment to JSON, where is the parent?
DDL of tables in a H2 Database
CREATE TABLE IF NOT EXISTS `author` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`created` TIMESTAMP NULL,
`modified` TIMESTAMP NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `comment` (
`id` INT NOT NULL AUTO_INCREMENT,
`review` VARCHAR(255) NOT NULL,
`author_id` INT NOT NULL,
`created` TIMESTAMP NULL,
`modified` TIMESTAMP NULL,
PRIMARY KEY (`id`),
INDEX `fk_comment_author_idx` (`author_id` ASC),
CONSTRAINT `fk_comment_author` FOREIGN KEY (`author_id`) REFERENCES `author` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE = InnoDB;
Parent entity
#JsonManagedReference("author_comment")
#OneToMany(mappedBy = "author", cascade = {CascadeType.ALL}, orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
Child entity
#JsonBackReference("author_comment")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author_id")
private Author author;
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;
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;