Java JPA enum-like String options - java

I am trying to implement a simple User-Roles relationship in a Spring application, for security. The basic entities (some fields and annotations trimmed):
User
#Table(name="usr")
public class User implements Serializable {
#Id
private UUID id;
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name="user_roles", joinColumns=#JoinColumn(name="user_id", referencedColumnName="id"),
inverseJoinColumns=#JoinColumn(name="role_id", referencedColumnName="id"))
private Collection<Role> roles;
}
Role
public class Role implements Serializable {
#Id
private UUID id;
#ManyToMany(mappedBy="roles")
private Collection<User> users;
private String name;
}
So far, so good. However, I also have a class that defines a list of role-name values:
UserRoles
public class UserRole {
public static final String ADMIN = "admin";
public static final String USER = "user";
}
I want to constrain the values of the Role's name field to the values in UserRoles, effectively like an enum.
These role values will get used within Spring Security functions that require roles to be string values. As such, if I were to make UserRoles an enum, any database storage would be of ints – the ordinal definition position within UserRoles – which would force me to keep any potentially deprecated options, and also require a hacky conversion every time I need to convert the role to a string that can be passed around in a JWT, etc. (If I want to look at my database directly, it will also be far less informative.)
Is there some way to define Role's name field as limited to the static values in UserRoles? (Changing how or where these values are stored is entirely acceptable.)

You can define like this
public enum UserRoleEnum {
USER, ADMIN
}
And in entity
#Enumerated(EnumType.ORDINAL)
private UserRoleEnum role;

Related

Data doesn't update immediately after changes in tables

I've recently tried to implement Spring Security into my web store project to distinguish between single users. Websites are working properly except there is one issue which I can't track to resolve. I have object called Customer within User class. Customer object has fields like id, balance, etc., and User has OneToOne relationship to Customer, so I can have single object for credentials and foreign key to specifics of user - his first name, last name, balance, owned products, etc.
I also have Product class which has ManyToOne relationship with Customer. It has its' own id, productCost, etc.
I'm using Spring MVC to take care of proper URL dispatching. When some action is taken, I'm using #AuthenticationPrincipal annotation to get currently logged Customer (through foreign key in User) and modify data regarding Customer linked with that foreign key.
When I modify Customer data through #AuthenticationPrincipal in controller, changes are immediate and they show up on website. But when I try to modify data through some DAO, for example by searching for Customer through id or try to get Customer that owns Product from Product getter (ManyToOne has reference to owning Customer), changes are not immediate. Database updates itself immediately and properly, like in first case, but collections in code and website state are not changed until I logout and login again - that's when data is updated. I suspect it may be due to fact that updating UserDetails updates data directly for currently logged user but then - how may I achieve same effect for Customer found by id?
Snippets of code:
Users.java:
#Entity
#Table(name="users")
public class Users {
#Id
#Column(name="username")
private String username;
#Column(name="password")
private String password;
#Column(name="enabled")
private boolean isActive;
#OneToMany(mappedBy="user")
private Set<Authorities> authorities;
#OneToOne
#JoinColumn(name="customer_id")
private Customer customer;
Product.java:
#Entity
#Table(name="product")
public class Product {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="name")
private String productName;
#Column(name="description")
private String productDescription;
#Column(name="category")
private String productCategory;
#Column(name="cost")
private int productCost;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="owner_id")
private Customer productOwner;
Customer.java:
#Entity
#Table(name="customer")
public class Customer {
//Class fields
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="balance")
private int balance;
#Column(name="first_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#Column(name="email")
private String email;
#OneToMany(mappedBy="productOwner", fetch=FetchType.EAGER)
private List<Product> ownedProducts;
Piece of controller code:
#Autowired
CustomerService customerService;
#Autowired
ProductService productService;
/*(...)*/
#GetMapping("/showOffer/{offerId}")
public String getOffer(#PathVariable int offerId, Model theModel, #AuthenticationPrincipal MyUserDetails user) {
Product retrievedProduct = productService.findById(offerId);
if (user.getCustomer().getBalance() >= retrievedProduct.getProductCost())
{
Customer retrievedProductOwner = retrievedProduct.getProductOwner();
/* This is where changes aren't applied immediately and I need to logout and login to process them. */
retrievedProductOwner.setBalance(1000);
/* This is where changes are immediately shown and Java collections are updated: */
user.getCustomer().setBalance(user.getCustomer().getBalance()-retrievedProduct.getProductCost());
/* Code below is an attempt to force immediate changes by updating collections directly from database - but that approach doesn't work */
productService.delete(retrievedProduct.getId());
retrievedProduct.getProductOwner().getOwnedProducts().clear();
retrievedProduct.getProductOwner().setOwnedProducts(productService.listOwnerProducts(retrievedProduct.getProductOwner()));
}
else {
System.out.println("Insufficient funds!");
}
return "redirect:/home";
TL:DR
I use UserDetails object in controller and I am also using DAO for Customer used as foreign key in UserDetails. Using UserDetails directly updates data and everything works fine, using DAO doesn't make changes until I logout and login.
as far as i understand your changes are only commited when you log out .
just try to synchronize and commit any modification at the right time and it would be safer that you manage sessions and transactions at the same time so you don't get any sort of incoherence when you do that. then tell me about the results .
Check whether CTRL+F5 in your browser (force cache clearance) updates your data similarly to logging out and back in. If so, it's a question of cached information. (this and (3) may occur at the same time)
Alternatively ... or perhaps complementarly ... your data fetch reqeust may be called before the database update/commit operation is completed. If so, it should become evident if you run distinct update and show routines. i.e. turn A into B, then into C, and you'd get something like B when you're expecting C... A instead of B... etc.
Lastly, depending on how you set up your back end, it is possible that you only populate whatever form you use for the front end exactly once, instead of dynamically querying the database whenever you access that form.

Can we use Composite Primary Key Mapping in spring data elastic search

I have an entity 'Product' and I want the primary key in ES to be used as a combination of 'id' and 'name' attributes. How can we do that using spring data elastic search.
public class Product {
#Id
private String id;
#Id
private String name;
#Field(type = FieldType.Keyword)
private Category category;
#Field(type = FieldType.Long)
private double price;
#Field(type = FieldType.Object)
private List<ValidAge> age;
public enum Category {
CLOTHES,
ELECTRONICS,
GAMES;
}
}
One way to achieve this would be the following:
first rename your id property, I changed it to documentId here. This is necessary, because in Spring Data
Elasticsearch an id-property can be either annotated with #Id or it can be namend id. As there can only be one
id-property we need to get this out of the way. It can have the name id in Elasticsearch, set by the #Field
annotation, but the Java property must be changed.
second, add a method annotated with #Id and #AccessType(AccessType.Type.PROPERTY) which returns the value you
want to use in Elasticsearch.
third, you need to provide noop-setter for this property. This is necessary because Spring Data Elasticsearchsoe
not check the id property to be read only when populating an entity after save or when reading from the index.
This is a bug in Spring Data Elasticsearch, I'll create an issue for that
So that comes up with an entity like this:
#Document(indexName = "composite-entity")
public class CompositeEntity {
#Field(name="id", type = FieldType.Keyword)
private String documentId;
#Field(type = FieldType.Keyword)
private String name;
#Field(type = FieldType.Text)
private String text;
#Id
#AccessType(AccessType.Type.PROPERTY)
public String getElasticsearchId() {
return documentId + '-' + name;
}
public void setElasticsearchId(String ignored) {
}
// other getter and setter
}
The repository definition would be straight forward:
public interface CompositeRepository extends ElasticsearchRepository<CompositeEntity,
String> {
}
Remember that for every method that needs an Elasticsearch Id, you'll need to create like it's done in the entity
class.
I am not sure about spring data elasticsearch but spring jpa provides the facility of defining composite primary key by using #IdClass where we can define a separate class(let us say class A) in which we can define all the fields which we want to be a part of composite key Then we can use #IdClass(A.class) in entity class and use #Id annotation on all the fields which should be the part of the composite key
you can refer to this article, although I am not sure whether the same concept will be applicable for spring data es - https://www.baeldung.com/jpa-composite-primary-keys

Hibernate : Mapping two entities

I have a question regarding mapping entities based on mapping in their tables.
So, we are a team of five working on a project, one of our team mate seem to add mapping between tables in opposite direction, I'll give examples of both to let you understand what I mean.
We have two tables User and UserInfo. UserInfo has a user_id as foreign key.
1) Common Mapping I have learnt about in hibernate.
In User and UserInfo entities I usually have mappings like this:
class User{
private int userId;
private String userName;
// getter and setters
}
class UserInfo{
private int userInfoId;
private String firstName;
private String lastName;
#OneToOne()
#JoinColumn(name="user_id")
private User user;
}
2) This is how my colleague does mapping:
class User{
private int userId;
private String userName;
#OneToOne(mappedBy="user")
#JoinColumn(name="user_id")
private UserInfo userInfo;
// getter and setters
}
class UserInfo{
private int userInfoId;
private String firstName;
private String lastName;
#OneToOne()
private User user;
}
He does just opposite of what I learnt from tutorials. It is working fine somehow but I am not sure if this is the right way to map two entities.
Please help.
Thanks.
both of them should create exact same tables in DB, but second solution is better when u need call user from userInfo or userInfo from user.
ex:
User user = ...
user.getUserInfo().getFirstName();
UserInfo info = ...
info.getUser().getUserName();
PS:
In this article says that most efficient to use #OneToOne with #MapsId
https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
The 2nd code snippet will absolutely work fine because you are not using a bi-directional relationship.
In case if you get JsonMappingException then simply you can handle by using below
annotation
You may use #JsonIgnore
The difference is that the one your colleague used is bidirectional and yours is unidirectional.
Bidirectional association provides navigation in both the directions. If you need userInfo object when you query user then bidirectional is what you need. If that's not the case, the one you have is more efficient than your colleague's.
I would recommend you to read this useful link on how to do one-to-one mapping efficiently:
https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/

JDBC Domain Design and Relationships

I've used Hibernate / JPA in the past, now using a combination of Spring JDBC and MyBatis.
With JPA/ Hibernate if you had a Customer, which had an address you would have a domain structure similar to code below. (minus all the annotations / config / mappings).
Does this still make sense when using JDBC or MyBatis. This is composition domain design from what I know, has-a, belongs-to, etc. However most examples I've seen of JDBC code they have domain object that bring back the IDs rather than collection, or flatten the data. Are there any performance benefits from either approach, maintainability, etc. Having worked with JPA first I'm not sure what the JDBC way of doing things are.
public class Customer {
private Long id;
private String userName;
private String password;
private String firstName;
private String lastName;
private Collection<Address> addresses
...
}
public class Address {
private Long id;
private String streetAddress1;
private String streetAddress2;
private String city;
private State state;
private String postalCode;
}
public class State {
private Long id;
private String code;
private String name;
private Country country;
}
public class Country {
private Long id;
private String code;
private String name;
}
I come across an example and here was one of their classes.
public class Question {
private long questionId;
private long categoryId;
private long userId;
private long areaId;
private String question;
private String verifyKey;
private Date created;
private User user;
private List<Answer> answers;
private long answerCount;
private String name;
// getters and setters omited...
}
Why would you fetch the userId, areaId, and categoryId instead of actually fetching the associated object? The ID is likely of no use to the front end user, I suppose you could use the ID to issue another query to fetch additional data, but seems inefficient making another round trip to the database.
You can look at this domain object as a "footprint" of database table. In your example, userId, areaId and categoryId from Question are most likely foreign keys from corresponding tables. You could never need full object data in the moment of Question creation and retrieve it later with separate db request. If you fetch all associated objects at once, you will hit at least one additional table per object (by join-s or subselect-s). Moreover, that's actually the same that Hibernate does. By default, it loads domain object lazily and hits database again if uninitialized associated object is needed.
At that time, it is better to fetch those objects that a domain object can't exist without. In your example, Question and List are coupled.
Of course, if you need user, or category, or any other associated object again in some another place of application (assume the reference to previously retrieved object has been lost), you will hit the database with same query. It should be done and could seem inefficient, because both plain JDBC and SpringJDBC have no intermediate caching unlike Hibernate. But that's not the purpose JDBC was designed for.

How to use projecting types in Spring Data MongoDB repository's query methods?

I have been playing around with Spring Data and MongoDB and have a question about limiting the amount of data for certain queries. I've seen adding custom queries within the MongoRepository, however I haven't seen any examples of limiting the amount of data and returning classes that are basically a subset of larger classes.
For instance I have a User class that has several fields, but I also want to create a UserShort class that has a subset of the fields in the User class. For instance UserShort would only contain the id and firstName / lastName / email fields, rather than everything.
I've seen I can specify/limit the fields that are returned, but can I have those returned into a different class? At the moment the UserShort will return null unless I specify the User class instead, but the fields will be limited to the ones I specify. Wasn't sure if this is possible at all? I realize the User class below isn't huge, but it's the concept I'm after.
A user interface:
public interface Users {}
Subset class:
public class UserShort implements Users {
#Id
private String id;
private String firstName;
private String lastName;
#Indexed(unique = true)
private String email;
//getters / setters
}
Full class:
#Document
public class User implements Users {
#Id
private String id;
private String firstName;
private String lastName;
private String username;
private String password;
private Date dob;
private Status status;
#Indexed(unique = true)
private String email;
private Gender gender;
private String locale;
private Date registerDate;
#DBRef
private List<UserAddress> addresses;
public User(){
addresses = new ArrayList<UserAddress>();
}
//getters / setters
}
And the repository interface:
public interface UserRepository extends MongoRepository<Users, String> {
public User findByEmail(String email);
#Query(value="{ 'email' : ?0 }", fields="{ 'firstName' : 1, 'lastName' : 1}")
public UserShort findUserShortByEmail(String email);
}
As long as the return type of the query method is assignable to the managed domain type (Users in your case) we will prefer the return type to determine the collection to run the query against. Thus, in your case we'd execute the query against userShort instead of users which is why you do not get any results. That behavior is in place to support storing inheritance hierarchies into different collections.
If you switched to User as the domain type for the repository, things should work exactly as expected. This would also have the benefit of preventing clients from handing UserShort instances to the save(…) method which will wipe out properties contained in User but not in UserShort. Here's the final repository interface declaration.
interface UserRepository extends MongoRepository<User, String> {
User findByEmail(String email);
#Query(value="{ 'email' : ?0 }", fields="{ 'firstName' : 1, 'lastName' : 1}")
UserShort findUserShortByEmail(String email);
}
P.S.: #byte-crunch outlined in the comments that this currently only works for collections of projections but not for returning single instances. This has been reported and fixed in DATAMONGO-1030 and will be available in 1.5.4 and 1.6.0 GA.

Categories