Mutually referential fields in Play - jpa

I am trying to model users with home directories in my system. I got the following model declarations:
#Entity
public class Directory extends Model {
public String name;
#ManyToOne public Directory parent;
#ManyToOne public User owner;
#OneToMany public Set<User> sharees;
}
#Entity
public class User extends Model {
#Unique #Column(unique=true) public String username;
public String password;
public Directory homeDirectory;
public User(String username, String password) {
this.username = username;
this.password = password;
this.homeDirectory = new Directory(username, null, this);
}
}
When I create a user and call .save(), I get an error (A javax.persistence.PersistenceException has been caught, org.hibernate.exception.GenericJDBCException: could not insert: [models.User]). Can anyone explain why?
Using fixtures, can I test this? I'd need to create forward references in my yaml file, but I'm not sure if that's possible.
Thanks,
Vincent.

The error is thrown because of a missing #OneToOne annotation for homeDirectory.
I assume you're creating a directory for each user. If that's the case, then you should also use CascadeType.ALL so these directories automatically get created/deleted when users get created/deleted.
And no Yaml does not support forward references,
so you'll have to work around that when using bidirectional relations.

Related

Schema-validation: missing table [game]

I think it may be possible dupplicate of this: Schema-validation: missing table [hibernate_sequences] but I can't figure it out.
So in my application.properties file I have this option: spring.jpa.hibernate.ddl-auto=validate and I receive this error:
Schema-validation: missing table [game]
Why I am receiving this?
Here is my Game class and User class:
Game:
#Entity
public class Game {
#Id
#Column(name = "GAME_NUMBER")
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long gameNumber;
private int playerScore;
private int NPCScore;
private Date datetime;
#ManyToOne
#JoinColumn(name="USER_ID")
private User user;
public Game() {}
public Game(int playerScore, int nPCScore, Date datetime) {
super();
this.playerScore = playerScore;
this.NPCScore = nPCScore;
this.datetime = datetime;
}
public User getUser() {
return user;
}
} + getters & setters
User:
#Entity
public class User {
#Id
#Column(name = "USER_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long userId;
private String username;
private String password;
#OneToMany(mappedBy="user",cascade=CascadeType.ALL)
private List<Game> games;
#ElementCollection
private List<Date> startSessions;
public User() {}
public User(String username, String password, List<Game> games, List<Date> startSessions) {
super();
this.username = username;
this.password = password;
this.games = games;
this.startSessions = startSessions;
}
}
validate validates that the entities are compatible against the target, to a degree it's not foolproof. Anyway, whatever database you are trying to validate against does not have a table called game in which to store the entities.
This answer goes into more detail about what validate does.
Hibernate - hibernate.hbm2ddl.auto = validate
specifically,
checks the presence of tables, columns, id generators
Without knowing your database/expectations (are you expecting it to be created, or using Flyway/Liquibase to create/update the database etc.) I can't answer if validate is correct for your use case.
You could try create-drop to create and drop the table on startup/shutdown, but this isn't a solution for any production control over a database.
I got the same as I changed to Hibernate 5.4.0.Final.
Either Hibernate suddenly has problems to recognize the default schema or the driver does not return the schema properly.
I was able to bypass it by either adding the schema definition to the table definition.
#Table(name = "GAME", schema = "PUBLIC")
or by adding a default schema in persistence.xml.
<property name="hibernate.default_schema" value="PUBLIC" />
Don't forget permissions:
GRANT select, insert, update, delete, alter ON table_name TO usr_name;
This error can appear while using spring boot with flywayDB.
The issue might be due to the wrong naming convention of script files, which were used by flywayDB.
https://flywaydb.org/documentation/migrations#naming
The SQL standard requires names stored in uppercase.
If you named the table/fields in lowercase - JPA can automatically convert case to upper and trying to search names in this case, but write to logs in lower ¯\_(ツ)_/¯
Add this in application.yml:
spring:
jpa:
properties:
hibernate:
default_schema: game
Hibernate version 5.6.9,
Case-sensitive implementation:
hibernate:
physical_naming_strategy: 'org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl'

Ebean is not cascading persistence with Play-2.5(OneToMany)

Here are my models.
User:
#Entity
#Table(name="users")
public class User extends Model {
String username;
#Id
String id;
#OneToMany(cascade = CascadeType.ALL)
List<Tag> tags;
}
Tag:
#Entity
#Table(name="tags")
public class Tag extends Model {
#Constraints.Required
public String tag;
}
Persistence code(Removed unnecessary code):
User user = new User();
user.id = UUID.randomUUID().toString();
user.username = username; // String
user.tags = tags; // list of tags;
Ebean.save(user);
I am calling Ebean.save(user) after adding tags to user object.
Tags added on user are not persisted to database. I am also not seeing any exception, other fields of user get persisted but not tags.
Am I missing something?
Note: I am using postgres.
Thanks for the suggestion #marcospereira.
I was missing id field in Tag model. After enabling debugging and sql logging I noticed warning in logs.
The correct way to create Tag class:
#Entity
#Table(name="tags")
public class Tag {
#Id
#GeneratedValue
public String id;
public String tag;
}
but It is weird why Ebean is doing that.
Hope this helps someone in future.

use JPA entity mapping while storing the entity but ignore fields or a particular table while reading back

I am using similar code to what is listed in: How to map one class with multiple tables in Hibernate/javax.persistance?
I was trying to write a sample login program, based on above example I map my user class to secondary table where I store password field. now when I retrieve back user entity. I also get secondary table field so password is also available in user object.
Is it possible, that during registration I want to use secondary table storage method but when I read back. it should not return password back with user?
How can I achieve this? I am looking for some JPA way like #transient ignore the particular column.
I wouldn't go for such implementation.
Best practice is to never store a clear-text password, but a digest instead:
#Entity
public class Account
{
#Column
private String username;
#Column(length = 32)
private String password;
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = DigestUtils.md5Hex(password);
}
}
It's an uncommon requirement, and JPA patterns will do their best to fight against you :)
But... some way may still be possible:
using Entity Listeners:
#Entity
public class Account
{
#Column
private String username;
#Column
private String password;
#PostLoad
public void postLoad()
{
password = null;
}
}
be careful: when loaded inside a transaction, a null password may be eventually flushed on commit.
removing getter for password:
if you put annotations only on fields, you can remove getPassword() method. Even if the field is populated on load, it's not accessible by external java code.
using a #Transient combination:
#Entity
public class Account
{
#Column
private String username;
#Column
private String password;
#Transient
private String password2;
public String getPassword()
{
return password2;
}
public void setPassword(String password)
{
this.password = password;
this.password2 = password;
}
}

Cannot change colum names in JPA Eclipselink

I'm having problems making #Column(name="example") working.
I've got a User class:
#Entity
public class User {
#Id
private String username;
....
}
A Role one:
#Entity
public class Role {
#Id
private String name;
....
}
That are in a ManyToMany relationship. So I created a RoleMembership:
#Entity
#IdClass(UserRolePK.class)
public class RoleMembership {
#Id
#ManyToOne
#PrimaryKeyJoinColumn(name="USERNAME")
private User user;
#Id
#ManyToOne
#PrimaryKeyJoinColumn(name="ROLE")
private Role role;
....
}
As you can see, the primary key is defined in UserRolePK:
public class UserRolePK{
#Id
#Column(name="ROLE")
private String role;
#Id
#Column(name="USERNAME")
private String user;
...
}
In this class I use #Column(name="USERNAME") and #Column(name="ROLE") to force its name to that string, but it's not working: JPA gives it the default names USER_USERNAME and ROLE_NAME (that are in TABLE_ID format).
Can anyone help me finding the mistake?
Thanks,
Andrea
EDIT:
I need to have three tables:
user (username (pk), password ....)
user_role (username, role_name)
role (name (pk), description)
I cannot change User definition in my model.
Remove all the annotations in UserRolePK and change your #PrimaryKeyJoinColumn annotations to #JoinColumn annotations.
Your usage of PrimaryKeyJoinColumn does not seem to make sense. I think you should be using just a #JoinColumn, or are you also mapping the columns as basics?
Perhaps include you complete class, and the DDL that is generated.

Avaje Ebean. ManyToMany deferred BeanSet

I am writing small app, using Play Framework 2.0 which uses Ebean as ORM.
So I need many-to-many relationship between User class and UserGroup class.
Here is some code:
#Entity
public class User extends Domain {
#Id
public Long id;
public String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public Set<UserGroup> groups = new HashSet();
}
#Entity
public class UserGroup extends Domain {
#Id
public Long id;
public String name;
#ManyToMany(mappedBy="groups")
public Set<User> users = new HashSet();
}
Database scheme generator generates good scheme for that code with intermediate table and all work quite ok, till I using many-to-many.
So I am adding group in one request:
user.groups.add(UserGroup.find.byId(groupId));
user.update();
And trying output them to System.out in another:
System.out.println(user.groups);
And this returns:
BeanSet deferred
Quick search show that BeanSet is lazy-loading container from Ebean. But seems like it doesn't work in proper way or I missed something important.
So is there any ideas about what I am doing wrong?
You need to save associations manually
user.groups.add(UserGroup.find.byId(groupId));
user.saveManyToManyAssociations("groups");
user.update();