Hibernate #ColumnTransformer How to set user defined key - java

In my Spring boot application I am using hibernate ORM and database as MySql, I have a user entity which has email column. Due to security reason we encrypt all the private informations so I want to encrypt the email using AES encryption (because AES encryption is used in the other component of application).
I was going through hibernate documentation & found #ColumnTransformer can be used for the same.
here is my Users Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "users")
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String customerNo;
#NotBlank(message = "email can't be blank")
#Email(message = "invalid format")
#ColumnTransformer(read = "AES_DECRYPT(UNHEX(gmailAddress), 'mySecretKey')", write = "HEX(AES_ENCRYPT(?, 'mySecretKey'))")
private String gmailAddress;
#CreationTimestamp
private LocalDateTime created;
#UpdateTimestamp
private LocalDateTime modified;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCustomerNo() {
return customerNo;
}
public void setCustomerNo(String customerNo) {
this.customerNo = customerNo;
}
public String getGmailAddress() {
return gmailAddress;
}
public void setGmailAddress(String gmailAddress) {
this.gmailAddress = gmailAddress;
}
public LocalDateTime getCreated() {
return created;
}
public void setCreated(LocalDateTime created) {
this.created = created;
}
public LocalDateTime getModified() {
return modified;
}
public void setModified(LocalDateTime modified) {
this.modified = modified;
}
}
This seems to be working, however I don't want to specify the key as #ColumnTransformer(read = "AES_DECRYPT(UNHEX(gmailAddress), 'mySecretKey')"
I would like to pass this value as a .key file which is also used by other components of application built on different language such as PHP.
I would like to know if there is a work around for this.
I am intermediate in spring boot and beginner in hibernate. Please do let me know if I need to provide any more information.

Related

Apply i18n to a Hibernate backed Spring Web API

I have a couple of Hibernate entities stored in a DB that I want to internationalize.
For example my "country" entity:
#Table(name = "country")
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "name")
private String name;
}
What I now want to do is enhance the API that handles the retrieval of the countries to return the country entry in the appropriate language. (e.g. adding ?lang=en to the query)
For that I have a country controller:
#RestController
#Api(tags = "Country")
public class CountryController {
private final CountryDao countryDao;
public CountryController(CountryDao countryDao) {
this.countryDao = countryDao;
}
#ApiOperation(value = "View a list of available countries.")
#GetMapping(path = "/entity/countries")
public Iterable<Country> getCountries() {
return countryDao.findAll();
}
}
How would I do that?
I have thought of adding a new i18n table that holds message keys with the available translations.
But in the end I still would like the API to return a simple JSON entry containing only the country name that the user is interested in, without having to attach all available translations.
I tried with AttributeConverter to mark the fields on the entity with #Convert(converter = I18nConverter.class)and search the i18n table for the matching key, but with that approach I don't have access to the specified language in the query...
public class I18nConverter implements AttributeConverter<String, String> {
private final I18nEntryDao i18nEntryDao;
#Override
public String convertToDatabaseColumn(String attribute) {
...
}
#Override
public String convertToEntityAttribute(String dbData) {
...
}
}

Hibernate creates two rows instead of one

pals.
I have an issue with Hibernate's JPA implementation. I use spring-boot-starter-data-jpa and PostgreSql v9.
I have two entities with bidirectional connection via OneToMany & ManyToOne:
#Entity
public class ShoppingCart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "shoppingCart", cascade = {CascadeType.ALL})
private List<Good> goods = new ArrayList<>();
public void addGood(Good good) {
good.setShoppingCart(this);
goods.add(good);
}
public Good removeGood(Good good) {
goods.remove(good);
good.setShoppingCart(null);
return good;
}
public ShoppingCart() {
}
public List<Good> getGoods() {
return goods;
}
public ShoppingCart(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
And second entity is
#Entity
public class Good {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cart_id")
#JsonIgnore
private ShoppingCart shoppingCart;
public ShoppingCart getShoppingCart() {
return shoppingCart;
}
public void setShoppingCart(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
public Good(String name) {
this.name = name;
}
public Good() {
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
Also I use CrudRepository to access ShoppingCart
public interface ShoppingCartRepository extends CrudRepository<ShoppingCart, Long> {}
And when I'm trying to fill existing cart I have two goods in my database. This is a code to add some goods into existing cart:
ShoppingCart cart = shoppingCartRepository.findOne(id);
cart.addGood(new Good("Butter"));
return shoppingCartRepository.save(cart);
In table "good" I have now two elements with different PKey and same data
5;"Butter";100
6;"Butter";100
Why it happens?
Also, when I'm trying to insert breakpoint at repository.save line, I see only one good in goods list in cart.
So, the problem is solved.
First way to solve is to make method with save code #Transactional.
Secon way is to use getGoods() instead of goods. We should change this code
public void addGood(Good good) {
good.setShoppingCart(this);
goods.add(good);
}
public Good removeGood(Good good) {
goods.remove(good);
good.setShoppingCart(null);
return good;
}
to this
public void addGood(Good good) {
good.setShoppingCart(this);
this.getGoods().add(good);
}
public Good removeGood(Good good) {
this.getGoods().remove(good);
good.setShoppingCart(null);
return good;
}
getGoods() here forces hibernate to update state of object and everything works fine.
As for me, I use both ways together
It happens because you create a new Good object without id. So Hibernate will generate a new id and persist the new object. If you don't want to create a new object, but only assign an already existing one, you either have to fetch the existing one from the database and assign it to the ShoppingCart oder add the ID if you create the new Good object.

Foreign key usage in Java with Hibernate

I am trying to develop a web application and I was wondering if there is a way to utilize a foreign key without writing a lot of code.
My Trainees.java
#Entity
public class Trainees {
#Id
#GeneratedValue
private int traineesID;
private int groupsID;
#ManyToOne
#JoinColumn(name = "status_trainee")
private String status_TraineeID;
private int customersID;
private String name;
private String surname;
private String phoneDetails;
private String email;
public Trainees(){
}
public Trainees(String name, String surname, String phoneDetails, String email, int id, int groupsID, String status_TraineeID, int customersID) {
super();
this.name = name;
this.surname = surname;
this.email = email;
this.phoneDetails = phoneDetails;
this.groupsID = groupsID;
this.status_TraineeID = status_TraineeID;
this.customersID = customersID;
}
//getters and setters
#Override
public boolean equals(Object object) {
if (object instanceof Trainees){
Trainees contact = (Trainees) object;
return contact.traineesID == traineesID;
}
return false;
}
#Override
public int hashCode() {
return traineesID;
}
}
Status_Trainee.java
#Entity
public class Status_Trainee {
#Id
#GeneratedValue
private int status_traineeID;
private String value;
public Status_Trainee(){
}
public Status_Trainee(String value, int id) {
super();
this.value = value;
}
//getters and setters
#Override
public boolean equals(Object object) {
if (object instanceof Status_Trainee){
Status_Trainee value = (Status_Trainee) object;
return value.status_traineeID == status_traineeID;
}
return false;
}
#Override
public int hashCode() {
return status_traineeID;
}
}
Error: Caused by: org.hibernate.AnnotationException: #OneToOne or #ManyToOne on uaiContacts.model.Trainees.status_TraineeID references an unknown entity: String
So my aim is that using the Trainees table and class, I could retrieve the value of Status_Trainee table using the foreign key. For example: if foreign keys ID is 2, then it would retrieve a string from status_trainee table where primary key would match the foreign key ID.
I am using models, controlers, hibernate and angularjs to display to the view, I don't really want to pass the table through all this, I thought using something like ManyToOne or JoinColumns would retrieve the value?
Thanks for all the help!
You should add a reference to StatusTrainee in Trainee and annotate that with OneToMany, ManyToOne or OneToOne. Depending on which kind of relationship you will need a list of StatusTrainee or just a StatusTrainee.
Tip: dont use underscores in class names.
First of all, it is not recommended to use "_" in a class name when using hibernate. Hibernate uses underscores when accessing foreignKeys. So Lets Say you rename your class to: TraineeStatus and the id change it to traineeStatusId..
Secondly, You can use the Hibernate annotations for what you need. but first you need to know how the relation is:
#OneToMany : One Trainee can have lots of statuses
#ManytoOne : Many trainees can have the same status
#OneToOne : one Trainee Can only have one status and the other way around.
Try this:
#Entity
public class Trainees {
#Id
#GeneratedValue
private int traineesID;
private int groupsID;
#OneToOne
private TraineeStatus status;
private int customersID;
private String name;
private String surname;
private String phoneDetails;
private String email;
...
You can change the #OneToOne for the one you need..
Remember that hibernate will try to map this in your Trainees mysql table as status_traineeStatusId, so if you have this column (as an integer) at your trainess table you are done :)..
That is it..
Hope it helps

Is this correct (better) object relational mapping?

I have really got stuck with using Hibernate in my project. Some issues I have described here: Hibernate: getting too many rows I have started wondering if my code is correct. I am working on a huge project and I have had to define mapping classes with annotations on my own. But when the problems have began to occur I have decided to recreate part of database separate to the project and try to generate entities in IDE.
I have two tables: My and Option. My has primary key: column qwerty and property. Propertyis the foreign key from Option table. And of course Option has property as a primary key.
In my solution I have created #Embeddable MyPK class with two properties: String qwerty and String property. In my My entity I have of course #EmbeddedId MyPK and also property (the same column name as in the MyPK) but is this Option object, not String as in the MyPK.
#ManyToOne
#JoinColumn(name = "property", nullable = false, insertable = false, updatable = false)
protected Option option;
This is entity generated by Hibernate Tools in Intellij Idea. There isn't EmbeddedId, but there is #IdClass. I have thought that #IdClass is only for basic types. But I have a object as a part of my primary key. However there is also OptionEntity object here. Is this correct to keep basic type and object type for one column?
#javax.persistence.IdClass(test.go.MyEntityPK.class)
#javax.persistence.Table(name = "MY", schema = "PUBLIC", catalog = "PUBLIC")
#Entity
public class MyEntity {
private String qwerty;
#javax.persistence.Column(name = "QWERTY")
#Id
public String getQwerty() {
return qwerty;
}
public void setQwerty(String qwerty) {
this.qwerty = qwerty;
}
private String text;
#javax.persistence.Column(name = "TEXT")
#Basic
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
private String lang;
#javax.persistence.Column(name = "PROPERTY")
#Id
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property= property;
}
#Override
public boolean equals(Object o) {
//equals
}
#Override
public int hashCode() {
//hashCode
}
private OptionEntity optionByProperty;
#ManyToOne
#javax.persistence.JoinColumn(name = "PROPERTY", referencedColumnName = "PROPERTY", nullable = false)
public OptionEntity getOptionByProperty() {
return optionByProperty;
}
public void setOptionByProperty(OptionEntity optionByProperty) {
this.optionByProperty = optionByProperty;
}
}
This is MyEntityPK generated class:
public class MyEntityPK implements Serializable {
private String qwerty;
#Id
#Column(name = "qwerty")
public String getQwerty() {
return qwerty;
}
public void setQwerty(String qwerty) {
this.qwerty = qwerty;
}
private String property;
#Id
#Column(name = "PROPERTY")
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
#Override
public boolean equals(Object o) {
//equals
}
#Override
public int hashCode() {
//hashCode
}
}
OptionEntity below. No special points aren't in this entity. I would like only #Version annotation on version property and also List<MyEntity> instead of Collection<MyEntity>.
#javax.persistence.Table(name = "OPTION", schema = "PUBLIC", catalog = "PUBLIC")
#Entity
public class OptionEntity {
private Long version;
#javax.persistence.Column(name = "VERSION")
#Basic
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
private String property;
#javax.persistence.Column(name = "PROPERTY")
#Id
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
#Override
public boolean equals(Object o) {
//equals
}
#Override
public int hashCode() {
//hashcode
}
private Collection<MyEntity> myByProperty;
#OneToMany(mappedBy = "optionByProperty")
public Collection<MyEntity> getMyByProperty() {
return myByProperty;
}
public void setMyByProperty(Collection<MyEntity> myByProperty) {
this.myByProperty = myByProperty;
}
}
What option is the most proper and less problematic? The one that I have described or the one that pasted?
get rid of property private String lang;/private String property; it is already defined by the manytoone. you also don't need a class for the primary key of MyEntity. MyEntity can be its own id class with the two properties qwerty and option as its key-property, key-manytoone.
Check out JPA 2.0 examples using derived Ids:
http://wiki.eclipse.org/EclipseLink/Examples/JPA/2.0/DerivedIdentifiers
which seem to be what you are after.
In JPA, you do not need an embeddedId for your ids, but if using a composite PK, you do need a class to hold the multiple values that make up the pk. Instances of this class are passed to the em.find method, and it can be either an EmbeddedId or a PKClass. I prefer using PKClass myself, but its up to you - using an embedded just places the fields within the embeddedable class, so you use the embedded object to set the mappings and access the values. If using a pkClass, you do not need the fields/properties annotated within it since they are accessed and mapped within the entity directly.

IllegalArgumentException in class: ..., getter method of property: id

I have two classes Role and Privilege with the relation ManyToMany. When adding a Privilege to a Role, and then calling saveOrUpdate(role), I get the exception below.
Here is the Role class:
#Entity
#Table(name = "ROLES")
public class Role implements GenericDomain {
private static final long serialVersionUID = -7620550658984151796L;
private Long id;
private String code;
private String name;
private Set<User> users = new HashSet<User>(0);
private Set<Privilege> privileges = new HashSet<Privilege>(0);
public Role() {
}
public Role(String code, String name) {
this.code = code;
this.name = name;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "ID")
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#Column(name = "CODE", unique = true, nullable = false, length = 16)
#NotEmpty(message= "password.required")
#Length(min = 3, max = 16)
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
#Column(name="NAME", nullable = false, length = 64)
#NotEmpty
#Length(min = 1, max = 32)
public String getName() { return name; }
public void setName(String name) { this.name = name; }
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "ROLES_PRIVILEGES"
, joinColumns = { #JoinColumn(name = "ROLE_ID") }
, inverseJoinColumns = { #JoinColumn(name = "PRIVILEGE_ID") }
)
public Set<Privilege> getPrivileges() {
return this.privileges;
}
public void setPrivileges(Set<Privilege> privileges) {
this.privileges = privileges;
}
/* overide of hascode, equals*/
}
Here is the Privilege class:
#Entity
#Table(name = "PRIVILEGES")
public class Privilege implements GenericDomain {
private static final long serialVersionUID = 4649689934972816194L;
private Long id;
private String code;
private Set<Role> roles = new HashSet<Role>(0);
public Privilege() {
}
public Privilege(String code) {
this.code = code;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "ID")
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#Column(name = "CODE", unique = true, nullable = false, length = 16)
#NotEmpty(message= "password.required")
#Length(min = 3, max = 16)
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
#ManyToMany(cascade=CascadeType.REFRESH, mappedBy="privileges")
public Set<Role> getRoles() {
return this.roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
/*overide equals and hascode*/
}
And here is the the exception:
IllegalArgumentException in class: com.stunaz.domain.Privilege, getter method of property: id
....
javax.persistence.PersistenceException: org.hibernate.PropertyAccessException: IllegalArgumentException occurred calling getter of com.stunaz.domain.Privilege.id
....
java.lang.IllegalArgumentException: object is not an instance of declaring class
It seems something is wrong with my mapping, that somewhere I should pass an object but I am passing an Id.
Hibernate is not much user friendly when it comes to telling user what's wrong with mapping.
Solution:
Debug the app
Set a breakpoint for the event of IllegalArgumentException being thrown (anywhere)
Perform the operation which causes this
Go up through the call stack and inspect the active stack variables.
Using this procedure, I figured out what was wrong with my mapping.
In general, from what I have seen, it's usually wrong class explicitely stated somewhere, like #MapKeyClass(Wrong.class) when the key is actually String etc.
Or, calling wrong (Hibernate) API, like setParameter() instead of setParameterList():
Query query = session.getNamedQuery(queryName);
// org.hibernate.PropertyAccessException: IllegalArgumentException occurred calling getter
query.setParameter("children", aCollectionOfChildren);
query.list();
Or, in case of Criteria API, I have seen this:
DetachedCriteria crit = DetachedCriteria.forClass( Group.class );
crit.add( Restrictions.eq( "parent", id ) );
The getParent() method of Group returns a Group but I was attempting
to compare it against a Long.
To get an illegal argument exception calling the getId() method, it seems that Hibernate thinks the type of your id is something other than Long (probably Integer). Maybe try adding #Type(type="long") to your ids.
Whenever I have weird issues with Hibernate, I always attach the source code and debug around where the error happens. This can give you some insight into what Hibernate is trying to do, and help figure out where you may have missed something, or passed a bad argument somewhere.
At first glance your code seems fine except maybe for:
#ManyToMany(cascade=CascadeType.REFRESH, mappedBy="privileges")
I'm not sure if this is incorrect but this is the way I do it:
#ManyToMany(cascade=CascadeType.REFRESH, mappedBy="privileges", targetEntity = Roles.class)
This mappedBy property could even be omitted...
How are you saving/updating this?
Are you getting the 'Role' object for which you want to save the 'Permission' by calling findById(Long roleId)? Once you get that role object create a new Permission object and setRole(role) and set other properties and callthe saveOrUpdate(permission)? That should work.
Just a note for others although this might be not related to this problem. It happened to me that I was mapping a DTO coming from a REST API to an Entity. One of the child DTOs was not mapped correctly to a child Entity (I was using Dozer). Hibernate failed because the id of the DTO was not compatible with the id of the Entity.

Categories