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

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.

Related

Jpa not inserting the record in the join table for many to many relatioship

I have a Many-to-Many relationship with User and Role JPA entities. When I try to save the entities, both User and Role entities gets persisted in the table, but the junction table is not getting inserted with the records, Where am I going wrong
User Entity
#Entity
#Table(name="users")
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "roles")
#ToString(exclude = "roles")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String password;
private double salary;
public User(String name, String password, double salary) {
super();
this.name = name;
this.password = password;
this.salary = salary;
}
#ManyToMany(
mappedBy = "users")
private Set<Role> roles = new HashSet<>();
public void addRole(Role role) {
this.roles.add(role);
role.getUsers().add(this);
}
}
Role Entity
#Entity
#Table(name = "roles")
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "users")
#ToString(exclude = "users")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String roleName;
public Role(String roleName) {
super();
this.roleName = roleName;
}
#ManyToMany
#JoinTable(
name="user_roles",
joinColumns = #JoinColumn(name="role_id", nullable = false),
inverseJoinColumns = #JoinColumn(name="user_id", nullable = false)
)
private Set<User> users = new HashSet<>();
}
Client class
#EventListener(ApplicationReadyEvent.class)
public void onApplicationStartup(ApplicationReadyEvent event) {
User kiran = new User("kiran", this.passwordEncoder.encode("welcome"), 4500000);
User vinay = new User("vinay", this.passwordEncoder.encode("welcome"), 4500000);
Role userRole = new Role("ROLE_USER");
Role adminRole = new Role("ROLE_ADMIN");
kiran.addRole(userRole);
vinay.addRole(userRole);
vinay.addRole(adminRole);
this.userRepository.save(kiran);
this.userRepository.save(vinay);
}
Where am I going wrong?
You've mapped a bidirectional relationship, but are only setting one side of it in your object model - the wrong side. Should there ever be a discrepancy, the owning side controls the values of foreign keys, and since you have left the owning side empty, they aren't being set. You are responsible to set both sides of relationships and keeping them in synch with what you want in the database.
Since you don't have cascade options set on the relationships, you are also responsible for persisting the roles independently from the Users. Something more like:
public void onApplicationStartup(ApplicationReadyEvent event) {
// you might want to check to see if these roles already exists and use those instead of creating new ones
Role userRole = roleRepository.save(new Role("ROLE_USER"));
Role adminRole = roleRepository.save(new Role("ROLE_ADMIN"));
User kiran = new User("kiran", this.passwordEncoder.encode("welcome"), 4500000);
kiran.addRole(userRole);//assumes this adds the user to the role.users as well.
this.userRepository.save(kiran);
User vinay = new User("vinay", this.passwordEncoder.encode("welcome"), 4500000);
vinay.addRole(userRole);
vinay.addRole(adminRole);
this.userRepository.save(vinay);
}
Also, you are using Set in your entities with Lombok using "#EqualsAndHashCode" generation. Don't do that!
Set uses the equals/hashcode logic to determine if two objects are the same to filter out duplicates, while Lombok generates those methods to use what are mutable fields. In the case you have new entities in those sets (ie this usecase), the IDs are null and will change when set from JPA. You are better off keeping Java equals/hashcode logic if you don't know what effects those will have on your application. try using either List in your model and/or not having Lombok generate those method for you.

JPA how to set foreign id - in Embeddable id - that is generated

My use case is: I have a Product with multi language name. To have at most one name per language a translation should be identified by locale + productId.
My problem is to get it working with a generated productId. This are the entities:
e.g. in oracle i get: "ORA-01400: cannot insert null into ."PRODUCT_NAME"."FOREIGN_ID""
Product:
#Entity
#Data
public class Product {
#Id
#GeneratedValue
private Long id;
#Column(unique = true)
private String articleNumber;
/**
* Localized name of the product.
*/
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#MapKey(name = "nameId.locale")
private Map<Locale, ProductName> names = new HashMap<>();
}
ProductName:
#Entity
#Data
public class ProductName {
#EmbeddedId
private TranslationId nameId;
private String name;
}
TranslationId:
#Embeddable
#Data
public class TranslationId implements Serializable {
private static final long serialVersionUID = 3709634245257481449L;
#Convert(converter = LocaleConverter.class)
private Locale locale;
private Long foreignId; //<-- this is null, should reference product
}
Is there a way to get this working without having to save the product first without the name? Without a generated id it is working of course - i just set same id for both.
I would like to re-use the translation id for other translated fields of other entities.

Effects of Apache Shiro's roles and permissions in existing Data Access Objects classes

I am using spring-boot-dependencies 1.3.5.RELEASE for my application and it runs on Java SE 1.8. I am using Apache Shiro' to mapusergroups inrolestouserpermissionswhereas I am usingDAO(Data Access Object`) for accessing data from database.
Let's say we have an entities such that
Employee "has-a" Department
Department "has-a" Domain
User "has-a" Domain
Entity Classes: Employee
#Entity
#Table(name = "EMPLOYEE")
#Data
#EqualsAndHashCode(of = "id", callSuper = false)
public class Employee {
#Id
#GeneratedValue
private int id;
private String name;
#ManyToOne
#JoinColumn(name="department_id")
private Department dept
}
Entity Classes: Department
#Entity
#Table(name="DEPARTMENT")
#Data
#EqualsAndHashCode(of = "id", callSuper = false)
public class Department {
#Id
#GeneratedValue
private int id;
private String name;
#ManyToOne
#JoinColumn(name="domain_id")
private Domain domain
#OneToMany(mappedBy="department")
private Set<Employee> employees;
}
Entity Classes: Domain
#Entity
#Table(name="DOMAIN")
#Data
#EqualsAndHashCode(of = "id", callSuper = false)
public class Domain{
#Id
#GeneratedValue
private int id;
private String name;
}
Now I would like to restrict User (a login user) to see only those Employees which are associated with the Departments whose Domain has an access to the User. Is there any way to achieve this without changing queries in DAO classes OR to do this with minimum code changes? Thank You.
Which Realm are you using? If you are using the JdbcRealm you should be able to set userRolesQuery to a query of your choice.

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.

How can I set foriegn key column in Spring 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.