Spring Boot: Prevent persisting field declared in superclass - java

I am creating a Todo app in Spring Boot and I need to create two tables: Task and Todo(Todo extends Task).
In Task table is a field called description and I would like to prevent that column to be created in Todo table.
How can I do it?
Task(parent):
package com.example.todo.model;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Task {
#Id
private long id;
private String name;
private String description;
}
Todo(child):
package com.example.todo.model;
import javax.persistence.Entity;
import javax.persistence.Transient;
#Entity
public class Todo extends Task {
private boolean isChecked;
}

I would suggest you clean up your design because concrete classes inheriting from other concrete classes is (often) a code smell. The proper solution to this is to factor out the common parts of both classes into a (abstract) super class and then add the specific fields to the concrete inheriting classes:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Completable {
#Id
private long id;
private String name;
}
#Entity
public class Task extends Completable {
private String description;
}
#Entity
public class Todo extends Completable {
private boolean isChecked;
}
so you have the behaviour grouped in the classes where it belongs and don't have to make sure that one thing contains a description while it shouldn't.

What you want cannot be done easily. But you might be trying to solve an issue in the wrong way.
From what I am reading you have a Task entity with has two separate types:
one with a checkbox indicating its completion
one with an additional description
If this is the case you might want to model the classes the same way. Thus having:
A Task entity without the description
A Todo entity extending Task with the checkbox
A new SummaryTask extending Task with a description field

Related

does lombok have side effects on jpa

I am working on converting a jpa entity to use lombok. The resulting code is the following:
#Entity
#Table(name = "TEST")
#Data
#NoArgsConstructor
#AllArgsConstructor
class Test {
...
#Column(name = "FORMATTING")
#Enumerated(EnumType.ORDINAL)
private FormatType formatType;
...
}
The resulting error message contains the following
Caused by: org.hibernate.HibernateException: Missing column: formatType in TEST
I am really not sure what to google here. (I tried pasting everything before formatType into google - didn't see anything)
NOTE:
fields have been renamed and aspects which do not appear relevant have been omitted, for the sake of brevity and privacy. if something looks like a typo, it probably is. please let me know if you notice something so that i can address it.
the 3 lines describing the field are unchanged from the code i'm working with
EDIT:
I just noticed this right before the error message
13:22:19,967 INFO [org.hibernate.tool.hbm2ddl.TableMetadata] (ServerService Thread Pool -- 57) HHH000261: Table found: TABLE
13:22:19,967 INFO [org.hibernate.tool.hbm2ddl.TableMetadata] (ServerService Thread Pool -- 57) HHH000037: Columns: [..., formatType, ...]
13:22:19,968 ERROR [org.jboss.msc.service.fail] (ServerService Thread Pool -- 57) MSC000001: Failed to start service jboss.persistenceunit."...": org.jboss.msc.service.StartException in service jboss.persistenceunit."...": javax.persistence.PersistenceException: [PersistenceUnit: ...] Unable to build EntityManagerFactory
Should be functional
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "PARENT")
public abstract class Parent implements Serializable {
private static final long serialVersionUID = 1;
#Id
#Column(name = "ID")
#GeneratedValue
private long id;
#Column(name = "ENABLED")
private boolean enabled;
}
#Entity
#Table(name = "CHILD")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Child extends Parent {
/** XXX: HERE BE DRAGONS */
#Column(name = "ENUM_1")
#Enumerated(EnumType.STRING)
private Enum1 enum1;
#Column(name = "ENUM_2")
#Enumerated(EnumType.ORDINAL)
private Enum2 enum2;
/** XXX: NO MORE DRAGONS */
#Column(name = "FREQUENCY")
private String frequency;
#Column(name = "EMPTY")
private boolean empty;
#Column(name = "MAX_SIZE")
private int maxSize;
}
public enum Enum1 {
A,
B,
C
}
public enum Enum2 {
X,
Y,
Z
}
I have rolled back the lombok changes, I would still like to know what the issue is, but there is no rush. Also, thanks to this lovely little bug i am about 4 hours behind so i may be a little slow on the responses.
The pk of the child table is an fk to the parent table, and without lombok everything appears to work, despite the fact that the Child class has no id.
SOLUTION:
I completely forgot about asking this. Not long ago I revizited this problem. To explain the solution lets look at a slightly simplified version of the first example i included.
#Entity
#Table(name = "TEST")
#Setter
#Getter
class Test {
...
#Column(name = "FORMATTING")
#Enumerated(EnumType.ORDINAL)
private FormatType formatType;
...
}
It would appear that Lombok will give you this:
#Entity
#Table(name = "TEST")
class Test {
...
#Column(name = "FORMATTING")
#Enumerated(EnumType.ORDINAL)
private FormatType formatType;
public FormatType getFormatType() {
return formatType;
}
public void setFormatType(FormatType formatType) {
this.formatType = formatType;
}
...
}
Note that the annotations are still attached to the field. Now, I am not certain if it is just the version or implementation of JPA that we are using but I gather that if an accessor is defined jpa just ignores any annotations besides #Column (as well as any parameters specified for #Column - which is why jpa was looking for the wrong column name). So we actually need:
#Entity
#Table(name = "TEST")
class Test {
...
private FormatType formatType;
#Column(name = "FORMATTING")
#Enumerated(EnumType.ORDINAL)
public FormatType getFormatType() {
return formatType;
}
public void setFormatType(FormatType formatType) {
this.formatType = formatType;
}
...
}
After a great deal of confusion trying to find examples and fill in some specifics regarding how lombok does its thing (to be fair I am very easily confused) i discovered this little gem: onMethod=#__({#AnnotationsHere}). Utilizing this feature I came up with the following:
#Entity
#Table(name = "TEST")
#Setter
class Test {
...
#Getter(onMethod=#__({
#Column(name = "FORMATTING"),
#Enumerated(EnumType.ORDINAL)
}))
private FormatType formatType;
...
}
And presto it works. Now that we have what is apparently the only available solution I would like to address the question we are all pondering at the moment: is that really any cleaner than just writing the method manually and attaching the annotations there? Answer: ... I have no idea. I am just happy I found a solution.
Its strange. Can you show more code?
I'm trying to write a simple project with part of code like in your question and it worked. I used Spring Boot and MySQL. Try to check your configuration. There is my code:
Enum:
public enum FormatType {
FIRST_TYPE, SECOND_TYPE
}
Table in MySQL:
create table TEST
(
ID int auto_increment primary key,
FORMATTING int not null
);
Entity:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#Entity
#Table(name = "TEST")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Test {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "FORMATTING")
#Enumerated(EnumType.ORDINAL)
private FormatType formatType;
}
Repository:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface TestRepository extends JpaRepository<Test, Integer> {
}
Service:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
#Service
public class TestService {
private TestRepository repository;
#Autowired
public TestService(TestRepository repository) {
this.repository = repository;
}
public List<Test> getAllTestEntities() {
return repository.findAll();
}
}
Is unlikely that lombok causes runtime problems, as it works on precompile time, you might find useful to decompile the generated code, I sometimes find that the order in which lombok annotations are placed in the source code affect the final result, so, you use #Data and #NoArgsConstructor , I guess you can remove #NoArgsConstructor an try to see if that solves your problem.
I faced the same problem with Lombok and JPA but I setup the Lombok and it worked as expected. Below is the code:
Controller
package com.sms.controller;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.sms.model.StudentModel;
import com.sms.persistance.StudentRepository;
#RestController
public class StudentController {
#Autowired
private StudentRepository sr;
#PostMapping("/addstudent")
public String addStudent(#Valid #RequestBody StudentModel studentModel) {
StudentModel result = sr.save(studentModel);
return result.equals(null)?"Failed":"Successfully Saved student data";
}
}
Model
package com.sms.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
import lombok.RequiredArgsConstructor;
#Data
#RequiredArgsConstructor
#Entity
#Table(name="student", schema="development")
public class StudentModel {
#Id
#Column(name="student_id")
private int id;
#Column(name="student_name")
private String studentname;
#Column(name="student_address")
private String studentaddress;
}
Repository
package com.sms.persistance;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.sms.model.StudentModel;
#Repository
public interface StudentRepository extends JpaRepository<StudentModel, Integer>{
}

Play 2.3: Ebean Inheritance Single-Table with OneToOne field

I have an abstract class to represent a type of settings. The inheritance type is in a single table as I wish to be able to access all types of settings irrespective of concrete type. Here is my parent abstract class:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING)
public abstract class Settings extends Model {
#Id
public Long settingId;
public static Model.Finder<Long, Settings> find = new Model.Finder<>(Long.class, Settings.class);
public abstract void run();
}
This is one of my concrete types:
#Entity
#DiscriminatorValue("text")
public class TextSettings extends Settings {
public boolean type;
#OneToOne(cascade = CascadeType.ALL)
public EmailFields emailFields;
public static Finder<Long, TextSettings> find = new Finder<>(Long.class, TextSettings.class);
public static TextSettings get() {
if (find.all().size() > 0)
return find.all().get(0);
else {
TextSettings settings = new TextSettings();
settings.emailFields = new EmailFields();
settings.emailFields.test = "Test"; \\this field is null if you try to get this field with a get on the TextSettings ebean object
settings.save();
return settings;
}
}
}
This concrete type actually contains another ebean model with the OneToOne relationship. Here is the code for that model:
#Entity
#DiscriminatorValue("email")
public class EmailFields extends Model {
#Id
public Long id;
public String test;
public static Finder<Long, EmailFields> find = new Finder<>(Long.class, EmailFields.class);
}
When I try to get the EmailFields model through the TextSettings model, I get the correct id and the object exists in the database, but the field test is null. Any field I add to it is always null.
This type of set up works for me in a non-inheritance ebean model so I can only think it has something to do with the single table. Does anyone know a solution for this, or will I have to copy the test field into the TextSettings model?
Note: I have simplified the code so logically it might not make sense as to why I have one field in EmailFields but the assumption is that I do need it as a separate model as some settings will have this model and some won't. So I don't want boilerplate code in those settings' classes.
Update
So for now I am using the #Embedded and #Embeddable annotations.
#Embeddable
public class EmailFields extends Model
And in TextSettings
#Embedded
public EmailFields emailFields;
This simply copies EmailFields' fields into the TextSettings object and not as a separate entity. Only drawback with this is that it increases the size of the table.

#OrderBy Annotation in Hibernate Not Working

import org.hibernate.annotations.OrderBy;
import javax.persistence.Column;
import javax.persistence.Entity;
public class A implements Serializable
{
#Id
#Column(name="SNR")
private long ASnr;
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="B")
private Set<B> bset;
public Set<B> getB() {
return this.bset;
}
#OrderBy(clause="bsnr DESC")
public void setB(Set<B> bset) {
this.bset = bset;
}
}
public class B implements Serializable
{
#Id
#Column(name="BSNR")
private long bsnr;
public long getBsnr() {
return this.bsnr;
}
public void setBsnr(long bsnr) {
this.bsnr= bsnr;
}
}
For above code #OrderBy is not working. When i try to make hibernate query then only select query is showing on console and there is no order by clause inside the sql query. Have i left something or written incorrect ?
you have used
import org.hibernate.annotations.OrderBy;
did you try with
import javax.persistence.OrderBy;
#OrderBy("bsnr")
I think you're misunderstanding what the #Orderby annotation actually does. According to the javadoc:
Specifies the ordering of the elements of a collection valued
association or element collection at the point when the association or
collection is retrieved.
The annotation does not dictate insertion order
Try adding on property or getter
I added antlr-2.7.7.jar in the classpath and used javax.persistence.OrderBy in code. Now it's working perfectly.

Objectify embedded array inside an embedded array can't persist

My Class heirarchy is as follows
School - contains list of Employees - which contains list of qualifications
Employees is an Embedded list in School. I can persist a School with it's employees no problem. Now when I add the list of qualifications to an employee as an embedded field I get the following error
You cannot nest multiple #Embedded arrays or collections
The objectify documentation seems to indicate I should be able to do this provided the objects are serializable which they are. Am I missing something? If this is the way it works is there a way around it?
Update:
School Class
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Embedded;
import javax.persistence.Id;
import com.googlecode.objectify.annotation.Entity;
#Entity
#SuppressWarnings("serial")
public class School implements Serializable
{
#Id
private String title;
#Embedded
private List<Employee> employees = new ArrayList<Employee>();
public School ()
{
}
public School (String title)
{
this.title = title;
}
public void addEmployee( Employee employee )
{
this.employees.add(employee);
}
}
Employee Class
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Embedded;
import javax.persistence.Id;
import com.googlecode.objectify.annotation.Entity;
#Entity
#SuppressWarnings("serial")
public class Employee implements Serializable
{
#Id
private String title;
#Embedded
private List<String> qualifications = new ArrayList<String>();
public Employee ()
{
}
public Employee (String title)
{
this.title = title;
}
public void addQualification( String qualification )
{
this.qualifications.add(qualification);
}
}
Caused by: java.lang.IllegalStateException: You cannot nest multiple #Embedded arrays or collections. A second was found at private java.util.List com.app.nquizitive.shared.Employee.qualifications
at com.googlecode.objectify.impl.save.EmbeddedMultivalueFieldSaver.<init>(EmbeddedMultivalueFieldSaver.java:36)
at com.googlecode.objectify.impl.save.EmbeddedCollectionFieldSaver.<init>(EmbeddedCollectionFieldSaver.java:21)
at com.googlecode.objectify.impl.save.ClassSaver.<init>(ClassSaver.java:64)
at com.googlecode.objectify.impl.save.EmbeddedMultivalueFieldSaver.<init>(EmbeddedMultivalueFieldSaver.java:43)
at com.googlecode.objectify.impl.save.EmbeddedCollectionFieldSaver.<init>(EmbeddedCollectionFieldSaver.java:21)
at com.googlecode.objectify.impl.save.ClassSaver.<init>(ClassSaver.java:64)
at com.googlecode.objectify.impl.save.ClassSaver.<init>(ClassSaver.java:29)
at com.googlecode.objectify.impl.Transmog.<init>(Transmog.java:322)
at com.googlecode.objectify.impl.ConcreteEntityMetadata.<init>(ConcreteEntityMetadata.java:75)
at com.googlecode.objectify.impl.Registrar.register(Registrar.java:69)
at com.googlecode.objectify.ObjectifyFactory.register(ObjectifyFactory.java:209)
at com.googlecode.objectify.ObjectifyService.register(ObjectifyService.java:38)
at com.app.nquizitive.server.dao.SchoolDao.<clinit>(SchoolDao.java:12)
There are two different annotations:
#Embed (#Embedded in ofy3)
#Serialize (#Serialized in ofy3)
If you want something to serialize, use the second. If you want something embedded, use the first. You can't nest #Embed(ded) lists, but you can put a #Serialize(d) list inside an embedded list.
Which of the classes above are annotated with #Entity? It sounds like School is a datastore entity, while Employees are not (i.e. they are just serialized into School) and qualifications are not (i.e. they are just serialized into Employees).
The Objectify annotation of #Embedded isn't needed/relevant/allowed, in a non-Entity class.

Objectify4 Order?

I upgraded my application to use Objectify4, but I can't get the ordering get working.
Here is what I did:
I have a class Offer which I want to query. This class is extended from Mail and from Model. The attribute for order should be the datetime which is indexed in the Mail-Class.
import com.googlecode.objectify.annotation.EntitySubclass;
import com.googlecode.objectify.annotation.Serialize;
#EntitySubclass(index=true)
public class Offer extends Mail {
private static final long serialVersionUID = -6210617753276086669L;
#Serialize private Article debit;
#Serialize private Article credit;
private boolean accepted;
...
}
import com.googlecode.objectify.annotation.EntitySubclass;
import com.googlecode.objectify.annotation.Index;
#EntitySubclass(index=true)
public class Mail extends Model {
private static final long serialVersionUID = 8417328804276215057L;
#Index private Long datetime;
#Index private String sender;
#Index private String receiver;
...}
import java.io.Serializable;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
#Entity
public class Model implements Serializable {
private static final long serialVersionUID = -5821221296324663253L;
#Id Long id;
#Index String name;
#Ignore transient private Model parent;
#Ignore transient private boolean changed;
...}
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyService;
public class DatabaseService {
static {
ObjectifyService.register(Model.class);
ObjectifyService.register(Mail.class);
ObjectifyService.register(Offer.class);
}
public static Objectify get() {
return ObjectifyService.ofy();
}
}
and that's what I want to do:
Query<Offer> result = DatabaseService.get().load().type(Offer.class).order("-datetime");
Unfortunely, the result is always NOT sorted.
Has anyone a hint?
At the low-level, this load operation has two parts:
filter by ^i = Offer
order by datetime desc
In order to make it work, you will need a multiproperty index like this:
<datastore-index kind="Model" ancestor="false">
<property name="^i" direction="asc"/>
<property name="datetime" direction="desc"/>
</datastore-index>
However, you are almost certainly abusing the datastore by making all your entities extend a polymorphic Model. You will have many problems in the future if you try to cram all of your entities into a single Kind; for one thing, practically every query will require a multiproperty index including the discriminator.
You can have a common base class, just don't make it the Kind. Keep the inheritance hierarchy, but move the #Entity up to (say) Mail. Offer can still have #EntitySubclass if you want a true polymorphic hierarchy there.
Read the objectify Concepts docs carefully and pick your Kinds carefully.

Categories