#Transient annotation, #org.springframework.data.annotation.Transient annotation, transient keyword and password storing - jpa

Currently I'm learning the Spring framework, mainly focusing on it's Security Module. I've watched some guides in connection with registration and login. I saw this common usage of transient keyword or #Transient annotation on the password field in the User class.
My dummy app is using Spring Boot + Spring MVC + Spring Security + MySQL.
I know that
Java's transient keyword is used to denote that a field is not to be serialized.
JPA's #Transient annotation...
...specifies that the property or field is not persistent. It is used to annotate a property or field of an entity class, mapped superclass, or embeddable class.
and the org.springframework.data.annotation's #Transient annotation...
Marks a field to be transient for the mapping framework. Thus the property will not be persisted and not further inspected by the mapping framework.
In my MySQL db I have my spring_demo schema which has 3 tables:
+-----------------------+
| Tables_in_spring_demo |
+-----------------------+
| role |
| user |
| user_role |
+-----------------------+
When I'm using the transient keyword on the password field int the User class, it would not be stored in the MySQL db. (example: test01)
mysql> select * from user;
+----+--------+------------------+----------+
| id | active | email | username |
+----+--------+------------------+----------+
| 1 | 1 | test01#gmail.com | test01 |
+----+--------+------------------+----------+
1 row in set (0,00 sec)
When I'm using the javax.persistence #Transient annotation on the password field in the User class, it also would not be stored in the MySQL db. (example: test02)
But... when I'm using the org.springframework.data.annotation #Transient annotation on the password field in the User class it does stored in the MySQL db. (example: test03) Why is that?
mysql> select * from user;
+----+--------+------------------+----------+--------------------------------------------------------------+
| id | active | email | username | password |
+----+--------+------------------+----------+--------------------------------------------------------------+
| 1 | 1 | test02#gmail.com | test02 | |
| 2 | 1 | test03#gmail.com | test03 | $2a$10$UbvmdhfcKxSNr/I4CjOLtOkKGX/j4/xQfFrv3FizxwEVk6D9sAoO |
+----+--------+------------------+----------+--------------------------------------------------------------+
2 rows in set (0,00 sec)
My main questions are, when I'm using the spring.data based #Transient annotation the password field has persisted. Why? And why should I use any #Transient annotation on a password field?
Thank you for your guidance and help in advance!

Within the Spring Framework you can use Mapping Framework to convert from one form to another. Say for example your spring java server side application needs send to user information to a client (webpage,mobile app) in JSON format.
#Entity
public class User {
#Id
private long id;
#Column(name = "username")
private String username;
#Column(name = "email")
private String email;
#Column(name = "password")
private String password;
}
Now to map this java entity object to JSON format you can either use a mapping framework (e.g jackson: com.fasterxml.jackson.databind.ObjectMapper) or do it manually.
The JSON format output that you would get when to convert user 2 object to JSON is:
{
"id": 2,
"email": "test03#gmail.com",
"username": "test03",
"password": "$2a$10$UbvmdhfcKxSNr/I4CjOLtOkKGX/j4/xQfFrv3FizxwEVk6D9sAoO"
}
Now if you added :
#org.springframework.data.annotation.Transient
#Column(name = "password")
private String password;
and then used the Mapping Framework to again generate the JSON for the user 2 entity you would get:
{
"id": 2,
"email": "test03#gmail.com",
"username": "test03",
}
Note the password field is missing from you JSON output. Thats because #org.springframework.data.annotation.Transient specifically states to the spring framework that the Object Mapper you are using should not include this value when converting from Java Object to JSON.
Also note if you attempted to persist the above entity into the database, it would still save it to the database because #org.springframework.data.annotation.Transient only applys to Object mapping frameworks not JPA.
So to recap:
transient is for all serializations (over the wire, saving to disk, saving to db)
javax.persistence.Transient is specifically for JPA DB serialization
#org.springframework.data.annotation.Transient is for ObjectMapping Framework serializations used within Spring

Related

JPA #SequenceGenerator with Manual ID and Auto ID

I have an entity
#Entity
public class Book {
#Id
#Column(name = "book_id")
#SequenceGenerator(name = "book_book_id_seq", sequenceName = "book_book_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "book_book_id_seq")
private Long id;
// getter, setter & other fields
}
with schema
CREATE TABLE book
(
book_id bigint NOT NULL DEFAULT nextval('book_book_id_seq'::regclass),
CONSTRAINT book_pkey PRIMARY KEY (book_id)
)
What I want to achieve is sometime I would like to use sequence/id generated by database, but sometime the data is created at other place and I would like to create with existing (manual) id.
I can't set the id manually with Spring Data JPA way (using CrudRepository) or JPA way (using EntityManager), but no issue with native query. Is this JPA limitation? Any workaround for my issue?
Book book01 = new Book();
bookRepo.save(book01); // Book with id 1 is created
Book book02 = new Book();
book02.setId(5555L);
bookRepo.save(book02); // Does not create book with id 5555, but 2
Query nativeQuery = entityManager.createNativeQuery("INSERT INTO book VALUES (6666);");
nativeQuery.executeUpdate(); // Book with id 6666 is created
Query nativeQuery02 = entityManager.createNativeQuery("INSERT INTO book DEFAULT VALUES;");
nativeQuery02.executeUpdate(); // Book with id 3 is created
I am using PostgreSQL 9.4, Hibernate 5 and Java 8.
On persist, if a field is annotated with #GeneratedValue, it will generate the ID with whatever strategy is specified. Then it will set value of the id field with the generated value. So if the id is manually set using setId() before persisting, this will just be overriden.
If you want, you can use em.persist for auto-generated IDs. Then use native SQL for manually setting the Id, since native SQLs will bypass whatever mapping you have on your entity.
Yes, by default Hibernate org.hibernate.id.SequenceGenerator always generate new id. What you should do is to override public Serializable generate(SessionImplementor session, Object obj) method, where if your obj (cast to your entity first) has id, then return the id, else get it from database sequence.

How to achieve #OneToOne User and role?

I have the following tables:
+-----------------------------------+
| user |
+------------------------------------
| id | username | role_id |
+----+-------------+----------------+
| 1 | user1 | 1 |
+----+-------------+----------------+
+-----------------------------------+
| role |
+------------------------------------
| role_id | role_name |
+-----------------+-----------------+
| 1 | GUEST |
+-----------------+-----------------+
| 2 | USER |
+-----------------+-----------------+
Each user have 1 role. Fields looks like this:
User.java
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="ROLE_ID")
private Role role;
Role.java
#Id
#GeneratedValue
#Column(name = "ROLE_ID", unique = true, nullable = false)
private int id;
The Role table will already be populated with only 2 roles. When I try to add a new user, a new record gets saved inside Role table. This is not the behavior I'm seeking. I don't want values to be written to Role table (unless maybe the the value being added doesn't exist in the table).
Any idea how to achieve this properly? Pretty sure this is simple, but I'm new to JPA.
edit: Code to add user
user = new User("JoDoe",new Role("USER"));
user = repository.save(user);
So as pointed out by the comments, it's because I'm creating a new Role every time. Can anybody point out the proper flow please? Should I first retrieve a Role from the db via RoleDao for example, and then assign that object to the new user? Or is there any other way that I'm missing?
First of all, since several users can have the same role, you don't have a OneToOne, but a ManyToOne.
Second, if you create a new Role, without even assigning it an ID, and create a User with that new Role, since the association has cascade = ALL, JPA will indeed create persist this new Role. How is it supposed to know that what you actually want to do is find the role which has the same name in database and assign that role to the new User?
That's what you must do. Assuming you have the ID of the role of the new User, what you need is
Role existingRole = em.getReference(roldId);
User user = new Role(existingRole);
em.persist(user);

Hibernate Envers - Adding Historical Data

Is there a way to add revision straight into the _AUD tables as a historical revision?
As I understand it as and when an entity is persisted envers creates a revision record in the _AUD table and keeping the current record in the entity table. Suppose I would like to add historical revision and not affect the entity table, is this possible?
For example, I have a Person entity
PERSON
ID | NAME
1 | SMITH
PERSON_AUD
ID | REV | REVTYPE | NAME
1 | 1 | 0 | SMITH
I would like to add the following in PERSON_AUD without modifying the PERSON table as SMITH is the current name.
PERSON_AUD
ID | REV | REVTYPE | NAME
1 | 1 | 0 | SMITH
1 | 2 | 2 | JONES
I'm afraid that's not possible with the current Envers API.
My background is C# but since the frameworks for Java are more powerful (at least I think this) I am sure you will find the related methods.
1) Reattaching the entity to the session will create a new revision:
private void Reattach(Person person)
{
_sessionContainer.Session.Transaction.Begin();
_sessionContainer.Session.Evict(person);
_sessionContainer.Session.Update(person);
_sessionContainer.Session.Flush();
_sessionContainer.Session.Transaction.Commit();
}
2) If you want to manipiulate the audit entry you need to append a listener (in this case for preupdate):
configuration.AppendListeners(ListenerType.PreUpdate, new object[] { new PreUpdateListener() });
where the implementation is doing the magic:
public class PreUpdateListener : IPreUpdateEventListener
{
public bool OnPreUpdate(PreUpdateEvent ev)
{
var person = ev.Entity as Person;
if (person != null)
{
person.Name = "Jones";
}
return true;
}
}
Please let me know if this approach is working for you.
You seem to be forgetting or not aware of the fact that revision number is global; it is tracked in a separate table which is called REVINFO in the default setup. Each entry for a revision number is accompanied by a timestamp as well. It is simply wrong (might even become a serious situation depending on the environment) to be inserting historical data that wasn't created naturally.
If you still want to do this, you can use native SQL to do it by creating a revision entry in REVINFO first and then using that in your audit table. Which is wrong.

JPQL Query Bulk UPDATE SET on an ElementCollection field

I have the following JPA Entity I want to update:
#Entity(name = "EmployeeImpl")
#Table(name = "EmployeeImpl")
public class EmployeeImpl {
#Id
#Column(name = "employeeId")
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ElementCollection
private List<String> phonenumber;
}
I thought I use a namedQuery like so
#NamedQuery(name = "updateEmployee",
query = "Update EmployeeImpl e SET e.phonenumber :number WHERE e.id = :id")
But that doesn't work: Exception Description: Error compiling the query [updateEmployee: Update EmployeeImpl e SET e.phonenumber = :number WHERE e.id = :id], line 1, column 28: invalid access of attribute [phonenumber] in SET clause target [e], only state fields and single valued association fields may be updated in a SET clause.
Question is, how do I update an #ElementCollection? If it's possible i'd like to do it with a jpql query.
No, that is not possible in JPQL. As kostja says: the message says it clear and also according to the JPA specification, Chapter "4.10 Bulk Update and Delete Operations" you may update only state fields and single values object fields.
The syntax of these operations is as follows:
update_statement ::= update_clause [where_clause]
update_clause ::= UPDATE entity_name [[AS] identification_variable]
SET update_item {, update_item}*
update_item ::= [identification_variable.]{state_field | single_valued_object_field} = new_value
new_value ::=
scalar_expression |
simple_entity_expression |
NULL
WHAT TO DO?
Probably the most clean way to do that is simply to fetch the entities and to add/replace the phone number/s, although you can always do that also with Native Queries, i.e SQL queries as kostja says.
The reason for the failure is stated in the error message. You cannot use bulk updates on non-singular attributes of your entity, since they are stored in a different table.
How do you do it instead? You update the collection table.
The default table name for collection tables is <parent_entity>_<field_name>. So the table you are interested in should be named EmployeeImpl_phonenumber. The id column for the EmployeeImpl (the foreign key) should be named EmployeeImpl_id per default.
EDIT What I posted initially was not valid in JPQL. You might want to use a native query instead. It is simple, so it should be portable:
The native query could then look like this:
UPDATE EmplyeeImpl_phonenumber
SET phonenumber = ?
WHERE employeeimpl_id = ?

How can I change the default JPA ID field in the Play Framework?

I'm currently creating some POJOs to connect to an existing database called User. The database already has a userid that I would like to use. However the framework creates an id field that it appends to the end of the table. How can I specify that the framework should use the pre-existing userid field and not create a new one?
You can use #AttributeOverride:
#Entity
#AttributeOverride(name = "id", column = #Column(name = "userid"))
public class User extends Model { ... }
To define your own primary key, have your models extend GenericModel instead of model and annotate your primary key with #Id. The model class enhances the generic model with an autogenerated id.
class User extends GenericModel{
#Id
Long userid;
}