JPA Audit Null CreatedBy - mongodb

I am using spring MVC with mongodb and using auditing entities to save the creation and last edition user and time. So my AudtingEntity, from which all objects inherit from, is like this:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.joda.time.DateTime;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.mongodb.core.mapping.Field;
/**
* Base abstract class for entities which will hold definitions for created, last modified by and created,
* last modified by date.
*/
public abstract class AbstractAuditingEntity {
#CreatedBy
#Field("created_by")
#JsonIgnore
private String createdBy;
#CreatedDate
#Field("created_date")
#JsonIgnore
private DateTime createdDate = DateTime.now();
#LastModifiedBy
#Field("last_modified_by")
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String lastModifiedBy;
#LastModifiedDate
#Field("last_modified_date")
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private DateTime lastModifiedDate = DateTime.now();
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public DateTime getCreatedDate() {
return createdDate;
}
public void setCreatedDate(DateTime createdDate) {
this.createdDate = createdDate;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
public DateTime getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(DateTime lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}
It works perfectly when I first save an object, but when I edit it the createdBy becomes null and the createdDate is updated also with the updating date. It happens because I'm ignoring these properties on front end and they are null when I save. A possible solution is to find the object in DB before save, then copy the properties to the updated object and save. However I don't like this solution because is needed to write this specific code for any object. I think probably there is a smarter way to solve this, configuring somehow to never update these fields in database after creation.
I tried to use the following property on these 2 fields, but didn't work:
#Column(updatable = false)

Related

JPA won't save an entity correctly unless the references to other entities are set

If I save this entity using JPA repository with a new defaultAssetId, it will only update the defaultAsssetId to the new value if I set defaultAsset as well. I want to be able to save without setting defaultAsset.
Same problem applies to taskType and assetRole.
package au.com.polonious.conf.entity;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
#Entity
public class TaskTypeAssetRole implements Serializable {
#GenericGenerator(name="tasktypeassetroleidseq",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
#Parameter(name = "sequence_name", value = "tasktypeassetroleidseq"),
#Parameter(name = "initial_value", value = "1"),
#Parameter(name = "increment_size", value = "1")
})
#Id
#GeneratedValue(generator = "tasktypeassetroleidseq")
private Long id;
#Column(insertable = false, updatable=false)
private Long taskTypeId;
#Fetch(FetchMode.JOIN)
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="tasktypeid")
private TaskType taskType;
#Column(insertable = false, updatable=false)
private Long assetRoleId;
#Fetch(FetchMode.JOIN)
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="assetRoleId")
private Role assetRole;
#Column(insertable = false, updatable=false)
private Long defaultAssetId;
#Fetch(FetchMode.JOIN)
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="defaultassetid")
private Asset defaultAsset;
private Date startDate;
private Date endDate;
private String notes;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getTaskTypeId() {
return taskTypeId;
}
public void setTaskTypeId(Long taskTypeId) {
this.taskTypeId = taskTypeId;
}
public TaskType getTaskType() {
return taskType;
}
public void setTaskType(TaskType taskType) {
this.taskType = taskType;
}
public Long getAssetRoleId() {
return assetRoleId;
}
public void setAssetRoleId(Long assetRoleId) {
this.assetRoleId = assetRoleId;
}
public Role getAssetRole() {
return assetRole;
}
public void setAssetRole(Role assetRole) {
this.assetRole = assetRole;
}
public Long getDefaultAssetId() {
return defaultAssetId;
}
public void setDefaultAssetId(Long defaultAssetId) {
this.defaultAssetId = defaultAssetId;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
}
I tried saving a taskTypeAssetRole with a new defaultAssedId without setting defaultAsset and I expected the defaultAssedId for that entry in the database to be updated.
What ended up happening was defaultAssetId didn't change although everything else in the entry did update successfully and there were no errors.
Your mapping is inherently broken. The column defaultassetiId is mapped to two different values: the field defaultAssetId and to the id of defaultAsset.
You should remove the defaultAssetId because this construct might break on any update of your JPA provider.
You can use references instead of full entities to set the reference values without loading entities from the database. See https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html#getReferenceById-ID-
If you don't follow this advice you should remove updatable=false if you want to update a field.

When using discriminator value for single table strategy, the first inserted entity's discriminator value is null but the value is there in database

When using discriminator value for inheritance/single table strategy, the first inserted entity's discriminator value is null but the value is there in the database.
I have to restart the server so that the query result containes the discriminator value:
package entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.MappedSuperclass;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="user_type", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue("Null")
#Table(name="ALLUSER")
#NamedQueries({
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u"),
#NamedQuery(name = "User.findByAccount", query = "SELECT u FROM User u WHERE u.account = :account")
})
public class User implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String account;
private String password;
private String userType;
public User() {
super();
}
public User(String account, String password) {
super();
this.account = account;
this.password = password;
}
#Id
#Column(name = "account")
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
#Column(name = "password")
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Column(name = "user_type", insertable = false, updatable = false, nullable = false)
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
#Override
public String toString() {
return account;
}
}
#Entity
#DiscriminatorValue("Normal")
#NamedQueries({
#NamedQuery(name = "NormalUser.findAll", query = "SELECT u FROM NormalUser u")
})
public class NormalUser extends User implements Serializable{
/**
*
*/
//private String account;
private static final long serialVersionUID = 1L;
private LinkedHashSet<Customer> customers;
public NormalUser() {
super();
}
#OneToMany(fetch=FetchType.EAGER, mappedBy="normalUser", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}) //eager can be optimized when deleting a normal user
public LinkedHashSet<Customer> getCustomers() {
return customers;
}
public void setCustomers(LinkedHashSet<Customer> customers) {
this.customers = customers;
}
// #Column(name = "account")
// //have to override in order to get account to use
// public String getAccount() {
// return account;
// }
//
// public void setAccount(String account) {
// this.account = account;
// }
}
If I just add a new normal user(child entity), then query this user whose user type will be null:
I use eclipse-link as the JPA implementation and Java EE three-tiered web architecture.
I know this definitely has something to do with the working of entity manager and persistence but I don't know the details. I also don't know how to resolve it. Any suggestion is welcome!
You are not setting the 'type' field within your entities, and JPA doesn't set it for you - not in the java object anyway. If it isn't set when you persist an entity, it will remain unset for as long as that entity is cached (locally or the shared EMF level cache). Restarting the app works because it clears the cache, forcing any fetches of existing entities to load from the database, where the type was set based on the discriminator column value.
You can set the type when creating the class, or force the data to be reloaded from the database by calling em.refresh on the instance. In this case though, it seems strange to even bother mapping the type column as a basic mapping - the getType method should just return the static discriminator value for the class, and you cannot change the type string anyway.

how to store PostgreSQL jsonb using SpringBoot + JPA?

I'm working on a migration software that will consume unknown data from REST services.
I already think about use MongoDB but I decide to not use it and use PostgreSQL.
After read this I'm trying to implement it in my SpringBoot app using Spring JPA but I don't know to map jsonb in my entity.
Tried this but understood nothing!
Here is where I am:
#Repository
#Transactional
public interface DnitRepository extends JpaRepository<Dnit, Long> {
#Query(value = "insert into dnit(id,data) VALUES (:id,:data)", nativeQuery = true)
void insertdata( #Param("id")Integer id,#Param("data") String data );
}
and ...
#RestController
public class TestController {
#Autowired
DnitRepository dnitRepository;
#RequestMapping(value = "/dnit", method = RequestMethod.GET)
public String testBig() {
dnitRepository.insertdata(2, someJsonDataAsString );
}
}
and the table:
CREATE TABLE public.dnit
(
id integer NOT NULL,
data jsonb,
CONSTRAINT dnit_pkey PRIMARY KEY (id)
)
How can I do this?
Note: I don't want/need an Entity to work on. My JSON will always be String but I need jsonb to query the DB
Tried this but understood nothing!
To fully work with jsonb in Spring Data JPA (Hibernate) project with Vlad Mihalcea's hibernate-types lib you should just do the following:
1) Add this lib to your project:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.2.2</version>
</dependency>
2) Then use its types in your entities, for example:
#Data
#NoArgsConstructor
#Entity
#Table(name = "parents")
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
public class Parent implements Serializable {
#Id
#GeneratedValue(strategy = SEQUENCE)
private Integer id;
#Column(length = 32, nullable = false)
private String name;
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private List<Child> children;
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private Bio bio;
public Parent(String name, List children, Bio bio) {
this.name = name;
this.children = children;
this.bio = bio;
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Child implements Serializable {
private String name;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Bio implements Serializable {
private String text;
}
Then you will be able to use, for example, a simple JpaRepository to work with your objects:
public interface ParentRepo extends JpaRepository<Parent, Integer> {
}
parentRepo.save(new Parent(
"parent1",
asList(new Child("child1"), new Child("child2")),
new Bio("bio1")
)
);
Parent result = parentRepo.findById(1);
List<Child> children = result.getChildren();
Bio bio = result.getBio();
You are making things overly complex by adding Spring Data JPA just to execute a simple insert statement. You aren't using any of the JPA features. Instead do the following
Replace spring-boot-starter-data-jpa with spring-boot-starter-jdbc
Remove your DnitRepository interface
Inject JdbcTemplate where you where injecting DnitRepository
Replace dnitRepository.insertdata(2, someJsonDataAsString ); with jdbcTemplate.executeUpdate("insert into dnit(id, data) VALUES (?,to_json(?))", id, data);
You were already using plain SQL (in a very convoluted way), if you need plain SQL (and don't have need for JPA) then just use SQL.
Ofcourse instead of directly injecting the JdbcTemplate into your controller you probably want to hide that logic/complexity in a repository or service.
There are already several answers and I am pretty sure they work for several cases. I don't wanted to use any more dependencies I don't know, so I look for another solution.
The important parts are the AttributeConverter it maps the jsonb from the db to your object and the other way around. So you have to annotate the property of the jsonb column in your entity with #Convert and link your AttributeConverter and add #Column(columnDefinition = "jsonb") as well, so JPA knows what type this is in the DB. This should already make it possible to start the spring boot application. But you will have issues, whenever you try to save() with the JpaRepository. I received the message:
PSQLException: ERROR: column "myColumn" is of type jsonb but
expression is of type character varying.
Hint: You will need to rewrite or cast the expression.
This happens because postgres takes the types a little to serious.
You can fix this by a change in your conifg:
datasource.hikari.data-source-properties: stringtype=unspecified
datasource.tomcat.connection-properties: stringtype=unspecified
Afterwards it worked for me like a charm, and here is a minimal example.
I use JpaRepositories:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Integer> {
}
The Entity:
import javax.persistence.Column;
import javax.persistence.Convert;
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
#Convert(converter = MyConverter.class)
#Column(columnDefinition = "jsonb")
private MyJsonObject jsonContent;
}
The model for the json:
public class MyJsonObject {
protected String name;
protected int age;
}
The converter, I use Gson here, but you can map it however you like:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class MyConverter implements AttributeConverter<MyJsonObject, String> {
private final static Gson GSON = new Gson();
#Override
public String convertToDatabaseColumn(MyJsonObject mjo) {
return GSON.toJson(mjo);
}
#Override
public MyJsonObject convertToEntityAttribute(String dbData) {
return GSON.fromJson(dbData, MyJsonObject.class);
}
}
SQL:
create table my_entity
(
id serial primary key,
json_content jsonb
);
And my application.yml (application.properties)
datasource:
hikari:
data-source-properties: stringtype=unspecified
tomcat:
connection-properties: stringtype=unspecified
For this case, I use the above tailored converter class, you are free to add it in your library. It is working with the EclipseLink JPA Provider.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.log4j.Logger;
import org.postgresql.util.PGobject;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Map;
#Converter
public final class PgJsonbToMapConverter implements AttributeConverter<Map<String, ? extends Object>, PGobject> {
private static final Logger LOGGER = Logger.getLogger(PgJsonbToMapConverter.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
#Override
public PGobject convertToDatabaseColumn(Map<String, ? extends Object> map) {
PGobject po = new PGobject();
po.setType("jsonb");
try {
po.setValue(map == null ? null : MAPPER.writeValueAsString(map));
} catch (SQLException | JsonProcessingException ex) {
LOGGER.error("Cannot convert JsonObject to PGobject.");
throw new IllegalStateException(ex);
}
return po;
}
#Override
public Map<String, ? extends Object> convertToEntityAttribute(PGobject dbData) {
if (dbData == null || dbData.getValue() == null) {
return null;
}
try {
return MAPPER.readValue(dbData.getValue(), new TypeReference<Map<String, Object>>() {
});
} catch (IOException ex) {
LOGGER.error("Cannot convert JsonObject to PGobject.");
return null;
}
}
}
Usage example, for an entity named Customer.
#Entity
#Table(schema = "web", name = "customer")
public class Customer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Convert(converter = PgJsonbToMapConverter.class)
private Map<String, String> info;
public Customer() {
this.id = null;
this.info = null;
}
// Getters and setter omitted.
If you're using R2DBC you can use dependency io.r2dbc:r2dbc-postgresql, and use type io.r2dbc.postgresql.codec.Json in your member attributes of an entity class, e.g.:
public class Rule {
#Id
private String client_id;
private String username;
private String password;
private Json publish_acl;
private Json subscribe_acl;
}

SonarQube (Sonar) + EclipseLink: incorrect error 'Comparison of String parameter using == or !='

I have the following class, using EclipseLink JPA:
package my.package;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Version;
import my.package.api.Address;
#Entity(name = "Address")
#SequenceGenerator(name = "sequence", sequenceName = "seq_address")
public class AddressJpaEntity implements Address {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence")
private Long id;
#Version
private Long version;
private String street;
public AddressJpaEntity() {
}
public AddressJpaEntity(String street) {
this.street = street;
}
#Override
public Long getId() {
return id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
#Override
public String getStreet() {
return street;
}
#Override
public void setStreet(String street) {
this.street = street;
}
}
When I do a SonarQube-run, I get a lot of the following (incorrect) errors:
Bad practice - Comparison of String parameter using == or !=
This code compares a java.lang.String parameter for reference equality using the == or != operators. Requiring callers to pass only String constants or interned strings to a method is unnecessarily fragile, and rarely leads to measurable performance gains. Consider using the equals(Object) method instead.
findbugs | ES_COMPARING_PARAMETER_STRING_WITH_EQ
Comparison of String parameter using == or != in my.package.AddressJpaEntity._persistence_set(String, Object)
For now, I solved it by setting the issues as false positive, but we will add more similar classes in the future and I don't want to do this each time.
How can I make Sonar not mark these errors, without using 'False positive' all the time?
Please check this post for a solution. It shows how to create an exclusion filter for FindBugs.
In this case, you'll want to ignore ES_COMPARING_PARAMETER_STRING_WITH_EQ warnings.
I still think it's incorrect to use ==, though, but apparently, that's not something you can help.

playframework JPA Error and DB design issue

I get the following error:
JPA error
A JPA error occurred (Unable to build EntityManagerFactory): #OneToOne or #ManyToOne on models.Issue.project references an unknown entity: models.Project
Here you can see my entities:
package models;
import java.util.*;
import javax.persistence.*;
import play.db.jpa.*;
import models.Issue;
import models.Component;
public class Project extends Model{
public String self;
#Id
public String key;
#OneToMany (mappedBy="Project", cascade=CascadeType.ALL)
public List<Component> components;
#OneToMany (mappedBy="Project", cascade=CascadeType.ALL)
public List<Issue> issues;
public Project(String self, String key) {
this.self = self;
this.key = key;
this.components = new ArrayList<Component>();
this.issues = new ArrayList<Issue>();
}
public Project addComponent(String self, int component_id, String name, int issuecount) {
Component newComponent = new Component(self, component_id, name, issuecount, this);
this.components.add(newComponent);
return this;
}
public Project addIssue(Date created, Date updated, String self, String key,
String type, Status status) {
Issue newIssue = new Issue(created, updated, self, key, type, status, this);
this.issues.add(newIssue);
return this;
}
}
and this is the other
package models;
import java.util.*;
import javax.persistence.*;
import play.db.jpa.*;
import models.Project;
import models.Status;
import models.Component;
#Entity
public class Issue extends Model {
#Id
public String key;
public Date created;
public Date updated;
public String self;
public String type;
#ManyToOne
public Status status;
#ManyToOne
public Project project;
#OneToMany
public List<Component> components;
public Issue(Date created, Date updated, String self, String key,
String type, Status status, Project project ) {
this.created = created;
this.updated = updated;
this.self = self;
this.key = key;
this.status = status;
this.type = type;
this.project=project;
this.components=new ArrayList<Component>();
}
public Issue addComponent(Component component) {
this.components.add(component);
return this;
}
}
I'm using Play 1.2.4 and Eclipse. Now my db is in mem.
I have also a second question. Ideally I need a db for each user and I want to delete the content of the tables every time the user logs in ( or logs out ) and populate the table again when the user logs in (this is because the information stored in my db must be in synch with service I'm connecting to ). How should I do?
I totally have no clue. Please help me.
public class Project extends Model
is missing the #Entity annotation
The "mappedBy" should reference the property in the other entity which is "project" and not "Project".