Placing #Column on field and #Transient on getter and setter at once - java

Please, explain for what purpose #Transient was placed on getter and setter methods at the same time as #Column was placed on field.
The field is stored to DB as we need.
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#Column(name = "name")
private String name;
#Transient
public long getName() {
return name;
}
#Transient
public void setName(String name) {
this.name = name;
}
}

this is actually a tricky situation. #transient annotation and transient keyword doesn't provide same functionality. using transient keyword will not serialize your data. but using #transient annotation will serialize the data but will not be persisted in the database, since you have marked the field with #column annotation that field will be persisted in the database because #Column allows you to specify the name of the column in the database to which the attribute is to be persisted.

In the code as you have it, they have no effect, since the #Id on a field causes the default access to be field, thus ignoring any method annotations. If property access was the default (either by #Id on the getter or #Access(PROPERTY) on the class), the #Transient annotations would cause JPA to ignore the accessors, presumably so that the field mapping could be picked up. However, in that case, the field should be annotated with #Access(FIELD).
I'd say the #Transient annotations are leftovers from a time when the entity had default property access.

I suspect its ignored since the column is marked already and as you say it is being updated as required. Enabling logging would confirm this as it would generate a warning
You could do a test creating a new test field and marking the getter/setter as Transient

Related

#JsonIgnore does not ignore fields in hibernate Entity

I have an Entity "Task" that has an Id property, but I don't need the field to be returned in the JSON file.
#Entity
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private Integer Id;
#JsonProperty("task")
private String taskName;
private String status;
//getter and setter
}
However, the annotation #JsonIgnore doesn't filter the field when I make the get request, see below:
{
"status": "started",
"timestamps": {
"submitted": "2018-12-31T00:34:20.718+0000",
"started": "2018-12-31T00:34:20.718+0000",
"completed": "2018-12-31T00:34:20.718+0000"
},
"id": 40001,
"task": "q094hiu3o"
}
What is the proper way to prevent "Id" to be displayed?
So here is the problem jackson had issue with hibernate issue, try using #jsonIgnoreProperties on class level
#JsonIgnoreProperties(ignoreUnknown = true,
value = {"id"})
You can try to add the #JsonIgnore only on your getter:
#JsonIgnore
public Integer getId() {
return id;
}
Also I would suggest to add the #JsonProperty annotation on your id field, if it is available in the Jackson version you are using:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty(access = Access.WRITE_ONLY)
private Integer id;
WRITE_ONLY
Access setting that means that the property may only be written (set) for deserialization, but will not be read (get) on serialization, that is, the value of the property is not included in serialization.
Jackson documentation here

JPA: How to handle versioned entities?

I have a versioning on an entity as part of its primary key. The versioning is done via a timestamp of the last modification:
#Entity
#Table(name = "USERS")
#IdClass(CompositeKey.class)
public class User {
#Column(nullable = false)
private String name;
#Id
#Column(name = "ID", nullable = false)
private UUID id;
#Id
#Column(name = "LAST_MODIFIED", nullable = false)
private LocalDateTime lastModified;
// Constructors, Getters, Setters, ...
}
/**
* This class is needed for using the composite key.
*/
public class CompositeKey {
private UUID id;
private LocalDateTime lastModified;
}
The UUID is translated automatically into a String for the database and back for the model. The same goes for the LocalDateTime. It gets automatically translated to a Timestamp and back.
A key requirement of my application is: The data may never update or be deleted, therefore any update will result in a new entry with a younger lastModified. This requirement is satisfied with the above code and works fine until this point.
Now comes the problematic part: I want another object to reference on a User. Due to versioning, that would include the lastModified field, because it is part of the primary key. This yields a problem, because the reference might obsolete pretty fast.
A way to go might be depending on the id of the User. But if I try this, JPA tells me, that I like to access a field, which is not an Entity:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToOne(optional = false)
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
#Column(nullable = false)
private boolean married;
// Constructors, Getter, Setter, ...
}
What would be the proper way of solving my dilemma?
Edit
I got a suggestion by JimmyB which I tried and failed too. I added the failing code here:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToMany
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private List<User> users;
#Column(nullable = false)
private boolean married;
public User getUser() {
return users.stream().reduce((a, b) -> {
if (a.getLastModified().isAfter(b.getLastModified())) {
return a;
}
return b;
}).orElseThrow(() -> new IllegalStateException("User detail is detached from a User."));
}
// Constructors, Getter, Setter, ...
}
What you seem to require seems to be on the lines of a history table, to keep track of the changes. See https://wiki.eclipse.org/EclipseLink/Examples/JPA/History on how EclipseLink can handle this for you while using normal/traditional JPA mappings and usage.
What you have here is a logical 1:1 relationship which, due to versioning, becomes a technical 1:n relationship.
You have basically three options:
Clean JPA way: Declare an 'inverse' #ManyToOne relationship from user to the "other object" and make sure you always handle it whenever a new User record is created.
'Hack-ish' way: Declare a #OneToMany relationship in the "other object" and force it to use a specific set of columns for the join using #JoinColumn. The problem with this is that JPA always expects unique reference over the join columns so that reading the UserDetail plus referenced User records should work, whereas writing UserDetail should not cascade onto User to avoid unwanted/undocumented effects.
Just store the user's UUID in the "other object" and resolve the reference yourself whenever you need it.
The added code in your question is wrong:
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
More correct, albeit not with the result you want, would be
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private User user;
This won't work though, because, as I said above, you may have more than one user record per UserDetail, so you'd need a #OneToMany relationship here, represented by a Collection<User>.
Another 'clean' solution is to introduce an artificial entity with a 1:1 cardinality w.r.t. to the logical User to which you can refer, like
#Entity
public class UserId {
#Id
private UUID id;
#OneToMany(mappedBy="userId")
private List<User> users;
#OneToOne(mappedBy="userId")
private UserDetail detail;
}
#Entity
public class User {
#Id
private Long _id;
#ManyToOne
private UserId userId;
}
#Entity
public class UserDetail {
#OneToOne
private UserId userId;
}
This way, you can somewhat easily navigate from users to details and back.
I came to a solution, that is not really satisfying, but works. I created a UUID field userId, which is not bound to an Entity and made sure, it is set only in the constructor.
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#Column(nullable = false)
// no setter for this field
private UUID userId;
#Column(nullable = false)
private boolean married;
public UserDetail(User user, boolean isMarried) {
this.id = UUID.randomUUID();
this.userId = user.getId();
this.married = isMarried;
}
// Constructors, Getters, Setters, ...
}
I dislike the fact, that I cannot rely on the database, to synchronize the userId, but as long as I stick to the no setter policy, it should work pretty well.

Jsonparsing error with #JsonIgnore on Hibernate

I get this exception when Jackson tries to parse my data to Json:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: packagename.Thing.Stuffs, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->packagename.Stuff[“thing"]->packagename.Thing[“stuffs"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: packagename.Thing.Stuffs, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->packagename.Stuff[“thing"]->packagename.Thing[“stuffs"])
I have the following entities(names have been replaced with Stuff and Thing):
Stuff:
#Entity
#Table(name = "stuff")
#SQLDelete(sql = "UPDATE stuff SET deleted = 1 WHERE id = ?")
#Where(clause = "deleted = 0")
public class Stuff implements Serializable {
private Long id;
private Thing thing;
private String stuffName;
#Id
#Column(name = "id", unique = true, nullable = false)
#GeneratedValue
public Long getId() {
return this.id;
}
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, optional = false)
#JoinColumn(name = "thing_id", nullable = false)
public Thing getThing() {
return thing;
}
#Transient
public String getStuffName() {
return stuffName;
}
// Setters and constructor(s) omitted
}
Thing:
#Entity
#Table(name = "thing")
public class Thing implements Serializable {
private Long id;
private String name;
private List<Stuff> stuffs;
#Id
#Column(name = "id", unique = true, nullable = false)
#GeneratedValue
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "name", unique = false, nullable = false, length = 45)
public String getName() {
return this.name;
}
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "thing_id")
#JsonIgnore
public List<Stuffs> getStuffs() {
return stuffs;
}
// Setters and constructor(s) omitted
}
I have been googling for this type of error. It seems that this is happening when the json-parser is trying to parse objects that aren't loaded due to lazy-load. Lazy load seems to be on by default. I'd like to avoid setting everything to eager so I put #JsonIgnore instead. Changes nothing for me. Help would be greatly appreciated, this is driving me nuts.
EDIT:
Adding #JsonIgnore and #Eager in both classes gives me another problem. An exception that is looking like this:
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.doResolveException Handling of [org.springframework.http.converter.HttpMessageNotWritableException] resulted in Exception java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
Searching for that on stackoverflow gives me this: Hibernate and Jackson (java.lang.IllegalStateException: Cannot call sendError() after the response has been committed)
The link is basically saying that I should add #JsonIgnore, and I have already done that.
From comments you said that you are using Jackson 1.9.10, which will allow you to use the new syntax of #JsonProperty annotation by adding READ_ONLY and WRITE_ONLY property for access type.
So you can use :
#JsonProperty(access = Access.WRITE_ONLY)
With your field definition.
For further details you can check Only using #JsonIgnore during serialization, but not deserialization discussion.
Note:
With older cersions you could just use #JsonIgnore on class member getter and #JsonProperty on its setter.
Maybe your problem caused by wrong mapping, you shouldn't use #JoinColumn for #OneToMany relation in Thing entity, you need to add mappedBy = "thing" as parameter to specify relationship correctly.

How properly annotate hibernate entities

Write some usual tests for my MVC webapp and stopped at findById() testing.
My model classes:
#Entity
public class Product {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private double purchasePrice;
private double retailPrice;
private double quantity;
#ManyToOne
#JoinColumn (name = "supplier_id")
private Supplier supplier;
#ManyToOne
#JoinColumn (name = "category_id")
private Category category;
#Entity
public class Category {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany
#Cascade(org.hibernate.annotations.CascadeType.ALL)
private List<Product> products;
#Entity
public class Supplier {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#LazyCollection(LazyCollectionOption.FALSE)
#Cascade(org.hibernate.annotations.CascadeType.ALL)
#OneToOne
private Contact contact;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany
private List<Product> products;
And my test code:
private Product productTest;
private Category categoryTest;
private Supplier supplierTest;
#Before
public void setUp() throws Exception {
categoryTest = new Category("Test category", "", null);
supplierTest = new Supplier("Test supplier", null, null);
productTest = new Product("Test product","", 10, 20, 5, supplierTest, categoryTest);
categoryService.save(categoryTest);
supplierService.save(supplierTest);
productService.save(productTest);
}
#Test
public void findById() throws Exception {
Product retrieved = productService.findById(productTest.getId());
assertEquals(productTest, retrieved);
}
Well, assertion failed, because of difference product.category.products and product.supplier.products properties, as you can see on pic:
One product have it as null, another as {PersistentBag}.
Sure I can easy hack it by writing custom equals method (which will ignore these properties), but sure it's not the best way.
So, why these fields are different?
I'm sure solution in properly annotation of entities fields.
Two pointers :
you use #LazyCollection(LazyCollectionOption.FALSE) in your relationship fields, so fields with that annotation are dynamically loaded by your ORM when you retrieve your entity while entites created in your fixture of your unit test are created outside from your ORM and you don't value these fields.
Even if you remove #LazyCollection(LazyCollectionOption.FALSE), you may have other differences if you want to do assertEquals() with a retrieved entity and a entity created by the hand. For example, with Hibernate, your lazy List will not be null but instance of PersistentList.
So, you should perform some works to perform assertions.
You may check properties individually or you may use Reflection to assert fields and ignore comparison for null fields in the expected object.
check http://www.unitils.org/tutorial-reflectionassert.html, it may help you.

Return complex type from Java Web service

I use Java Persistence, and I want a web method to return a 'portion' of an Entity class. For example, I have a Customer class that represents a record in Customer table with many fields, but I want to return just few of them. Is it possible to use mapping to do that? Or the only way is to create a new class (maybe a superclass for Customer) that has only fields I want to return? I tried binding, but it didn't work (apparently I did it in a wrong way):
#Entity
#Table(name = "Customer", catalog = "test", schema = "")
#XmlType(name = "Customer")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
#XmlElement
private Integer accountId;
#Basic(optional = false)
#Column(name = "username")
#XmlElement
private String username;
#Basic(optional = false)
#Column(name = "password")
private String password;
I thought that if I don't add #XmlElement annotation to password field, it won't be included into result. However, I got a bunch of "Class has two properties of the same name" errors during deployment.
This is because the default behaviour for XML generation is PUBLIC_MEMBER (http://java.sun.com/javaee/5/docs/api/javax/xml/bind/annotation/XmlAccessorType.html).
Since you are putting the #XmlElement on the fields, it is grabbing both your public getter/setter methods AND any field w/ #XmlElement on it. What you're likely going to want to is set the XmlAccessorType to either FIELD or NONE.
Annotate the class with
#XmlAccessorType(XmlAccessType.NONE)
Then annotate the fields you want to send with
#XmlElement(name="field_name")
There's actually a JAXB issue (that I can't find the reference to right now) that basically says that if you will be reading from XML, you'll want to annotate the setters and not the fields themselves.

Categories