How can I set foriegn key column in Spring JPA - jpa

I am using Spring JPA and want to set value to a foreign key column. Here is my entities and repository.
#Entity
public class Device {
#NotEmpty
#Id
private String deviceId;
#ManyToOne
#JoinColumn(name="userId", referencedColumnName="userId", insertable=false, updatable=false)
#NotFound(action=NotFoundAction.IGNORE)
private User user;
//Getters and setters
}
#Entity
public class User(){
#Id
private String userId;
private String userName;
//Getters and setters
}
public interface DeviceRepository extends PagingAndSortingRepository {
}
public class DeviceServiceImpl implements DeviceService {
#Autowired
private DeviceRepository devRepos;
#Autowired
private UserRepository userRepos;
#Override
public void saveDevice(Device device, String userId) {
User user = null;
if (userId!=null) {
user = userRepos.findOne(userid);
device.setUser(user);
}
deviceRepos.save(device);
}
}
The user exists in Device table but userId column in the table does not set the value. Please help me to fix the problem.
EDIT:
I removed insertable and updatable from the annotation and now it works.
#JoinColumn(name="userId", referencedColumnName="userId")
Then, this means I have to get user of the device from the User table whenever I save a device?

Because you set insertable and updatable to false for the user property in your Device class , it will cause the persistence provider to ignore this column (Device.userId) when generating SQL INSERT and UPDATE statement.
Just change them to true or remove them as their default values are already true.
Update :
this means I have to get user of the device from the User table
whenever I save a device?
In pure JPA , if you know the ID of the user , you can use EntityManager#getReference(User.class , aUserId) to get an User instance without actually querying from DB . But in Spring Data JPA , it seems that this method is not supported out of the box.

Related

Is there a way to automatically avoid duplicate data insertion in SpringBoot with JPARepository and Postgres?

so my AppUser entity is like this :
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "Employee")
public class AppUser {
// public enum Roles {ADMIN,COLLABORATOR,PROJECT_MANAGER,MANAGER,ACCOUNTANT}
//TODO : add default values for fields that should not be null?
#Id #GeneratedValue(strategy = IDENTITY)
private Integer id;
private String first_name;
#Column(unique = true)
private String username;
private String password;
#Column(name="cnss")
private Boolean cnss;
#ManyToMany(fetch= FetchType.EAGER) // when we load a user, we load their roles
private Collection<Role> roles= new ArrayList<>();
}
in my main app i ran this code:
usserService.saveUser(new AppUser(null,"John Doe","Tom","1234",null,new ArrayList<>()));
userService.saveUser(new AppUser(null,"Michael","Michael","xyz",true,new ArrayList<>()));
userService.saveUser(new AppUser(null,"Emma Stone","Emma","1234",true,new ArrayList<>()));
userService.saveUser(new AppUser(null,"Jack Smith","Jack","a1b2c3",false,new ArrayList<>()));
which works fine, but when I rerun the app again , the same users get duplicated again with different ids (so I have 8 users in total).
I want to know if there is a way to handle this without explicitly having to write if statements and checking manually of the username already exists or not.

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'

spring data jpa fine granular auditing, custom audit

I have requirement where I need to insert user name and group name to which the user belongs (both available in SecurityContext) in the same table.
class Entity
{
#createdBy
String username
#createdBy
String groupname
other fields ...
}
As per requirement. I cant solve this issue by making a user class and referencing it through a foreign key.
With current implementation of AuditingHandler both fields are getting the same value. How do I make sure they get respective values.
Can this be achieved using current implementation ?
If not thn how can I provide custom implementation of AuditingHandler ?
You could make a separate embeddable class and annotate it with #CreatedBy in your parent class. One way is to define a bean implementing AuditorAware, then you can make it return custom object, containing your two required fields. For example, your parent class would look like this (note the listener annotation):
#Entity
#EntityListeners(AuditingEntityListener.class)
public class AuditedEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
private String id;
#Embedded
#CreatedBy
private AuditorDetails createdBy;
// setters and getters
}
where AuditorDetails is:
#Embeddable
public class AuditorDetails {
private String username;
private String groupname;
// setters and getters
}
and finally, your AuditorAware bean:
#Component
class AuditorAwareImpl implements AuditorAware<AuditorDetails> {
#Override
public AuditorDetails getCurrentAuditor() {
return new AuditorDetails()
.setUsername("someUser")
.setGroupname("someGroup");
}
}
AuditingHandler fetches your custom AuditorDetails from your AuditorAware bean (it must be single bean implementing it) and sets it in your auditable entity.

How to map existing JPA entities to PicketLink

I am trying to migrate a Seam 2 app to CDI and use PicketLink for security. After all the reading and researching, it seems like all the examples are having one to one mapping between PicketLink model and the backend entity. e.g. Account to AccountEntity, Partition to PartitionEntity. Since I already have entities in place representing identity model, I am stuck on trying to map them to PicketLink. Here is what I have:
#MappedSuperClass
public class ModelEntityBase implement Serializable {
#Id #Generated
Long id;
Date creationDate;
}
#Entity
public Account extends ModelEntityBase {
String username;
String passwordHash;
#OneToOne(mappedBy = "account")
Person person;
}
#Entity
public Person extends ModelEntityBase {
String name;
String email;
#OneToOne
#JoinColumn(name = "account_id")
Account account;
}
Two entities (plus a super class) representing a single identity model in PicketLink, e.g. stereo type User.
Based on this why IdentityType id is String not Long, I tried to add a new Entity in:
#Entity
#IdentityManaged(BaseIdentityType.class);
public class IdentityTypeEntity implement Serializble {
#Id #Identifier
private String id;
#OneToOne(optional = false, mappedBy = "identityType")
#OwnerReference
private Account account;
#IdentityClass
private String typeName;
#ManyToOne #OwnerReference
private PartitionEntity partition;
}
I've tried a few different ways with the annotation and model classes. But when using IdentityManager.add(myUserModel), I just can't get it to populate all the entities. Is this even possible?
Got help from Pedro (PicketLink Dev). Post the answer here to help others.
This is the model class I ended up using.
#IdentityStereotype(USER)
public class User extends AbstractAttributedType implements Account {
#AttributeProperty
private Account accountEntity;
#AttributeProperty
#StereotypeProperty(IDENTITY_USER_NAME)
#Unique
private String username;
#AttributeProperty
private boolean enabled;
#AttributeProperty
private Date createdDate;
#AttributeProperty
private Date expiryDate;
#AttributeProperty
private Partition partition;
// getter and setter omitted
}
And created a new entity to map to this model:
public class IdentityTypeEntity implements Serializable {
#Id
#Identifier
private String id;
#OneToOne(optional = false, mappedBy = "identityType",
cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#AttributeValue
// #NotNull
private HAccount accountEntity;
#IdentityClass
private String typeName;
#ManyToOne
#OwnerReference
private PartitionEntity partition;
#AttributeValue
private String username;
#AttributeValue
// #Transient
private boolean enabled;
#AttributeValue
private Date createdDate;
#AttributeValue
private Date expiryDate;
}
PL can map property with #AttributeProperty to entity property with #AttributeValue. But it can only map to one entity. Therefore there is no way to map, say User and its properties over to Account and Person. But you can have the entity (in my case accountEntity) in the model. I also have to duplicate a few fields in the new IdentityTypeEntity and my existing Account entity (username, eanbled, createdDate) because PL requires these. Use a #PrePersist and similar to sync them.

ebean unidirectional #OneToOne relation with unique constraint

I have a User class:
#Entity
public class User extends Model {
#Id
public Long id;
public String email;
public String name;
public String password;
}
and a driver class
#Entity
public class Driver extends Model {
#Id
public Long id;
#OneToOne (cascade = CascadeType.ALL)
#Column(unique = true)
public User user;
}
I want to make sure that the user_id is unique inside the Drivers table. But the code above does not enforce that. (I can create multiple drivers with the same user id).
Ideally, I do not want to add the #OneToOne relations in the User class because there are several different roles inside my app (e.g. driver, teacher, agent etc.) and I don't want to pollute user class with all those relations.
How can I achieve this?
I have tried this code on the model for me, and it worked. One thing to be noted, that you must use #OneToOne annotation to let the ORM knows that you have foreign key reference to other model.
The model look like following:
#Entity
// add unique constraint to user_id column
#Table(name = "driver",
uniqueConstraints = #UniqueConstraint(columnNames = "user_id")
)
public class Driver extends Model {
#Id
public Long id;
#OneToOne
#JoinColumn(name = "user_id")
public User user;
}
It will generate evolution script like this :
create table driver (
id bigint not null,
user_id bigint,
constraint uq_driver_1 unique (user_id), # unique database constraint
constraint pk_driver primary key (id)
);
So, with this method you can make sure that you will have unique user reference on driver table.
Additional Info
Because there is an additional constraint, that is not handled by framework but by the database applied on the model (such as the unique constraint), to validate the input or handling the occurred exception, you can surround Model.save() or form.get().save() expression (saving-the-model) with try-catch block to handle the PersistenceException.