How to put multiple inherited entities in a single table? - java

I have four classes which should put in one db table.
First class represents basic information.
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "TYPE")
#Entity
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class A {
#Id
private String id;
private LocalDateTime date;
private String someString;
}
The second class extends class a and has some extra properties.
#Inheritance
#MappedSuperclass
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class B extends A {
private String extraProperty;
}
Finally there are two parallel classes with concrete type information.
#Entity
#DiscriminatorValue(value = "C1")
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class C1 extends B {
private String property1;
private String property2;
}
#Entity
#DiscriminatorValue(value = "C2")
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class C2 extends B {
private String propertyX;
private String propertyY;
}
The ReST repository looks like this:
#RepositoryRestResource(
path = "items",
collectionResourceRel = "items"
)
public interface ItemRepository extends PagingAndSortingRepository<A, String> { }
Now I have written a test where the expected format is tested.
#Test
public void shouldReturnItemlistWithCorrectDataStructure() throws Exception {
mockMvc.perform(get("/Items"))
.andExpect(jsonPath("$._embedded.items").isArray())
.andExpect(jsonPath("$._embedded.items", hasSize(2)))
.andExpect(jsonPath("$._embedded.items[0].id").value("1234567"))
...
}
I expected one array (items[]) in the result json but there are actually two different arrays c1[] and c2[].
Any ideas what I'm doing wrong?

Related

Mapping AbstractEntity to SubClassDTO - Single Table Strategy

I'm trying to mapp Entity (TrainingEntity) to DTO, where one of the fields is a Set with ManyToMany reference to an AbstractEntity (CoachEntity) divided by Single Table into two subclasses: ExternalCoach and InternalCoach.
Both subclasses have different data, therefore require two different mappers.
#Entity
#Table(name = "TRAINING")
public class TrainingEntity extends AbstractEntity {
#ManyToMany()
#JoinTable(name = "TRAINING_EMPLOYEE", joinColumns = { #JoinColumn(name = "TRAINING_ID") }, inverseJoinColumns = {
#JoinColumn(name = "COACH_ID") })
private Set<CoachEntity> coachEntities;
#Column(nullable = false)
private TrainingType trainingType;
......some data....
}
Abstract Coach Entity
#Entity
#Table(name = "COACH")
#DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.STRING)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class CoachEntity extends AbstractEntity {
......some data....
}
Coach Subclasses:
#Entity
#DiscriminatorValue("EXTERNAL")
public class ExternalCoachEntity extends CoachEntity {
......some data....
}
#Entity
#DiscriminatorValue("INTERNAL")
public class InternalCoachEntity extends CoachEntity {
......some data....
}
One mapper for Abstract Coach class won't have acces to subclasses methods and fields, so I need two different for External and Internal. Than I would have to use them in TrainingMapper class, but (example of internal):
public class CustomTrainingMapper {
public static TrainingDto toTrainingDto(TrainingEntity trainingEntity){
if(trainingEntity == null){
return null;
}
if(trainingEntity.getTrainingType().equals(TrainingType.INTERNAL)){
Set<CoachEntity> coachEntities = trainingEntity.getCoachEntities();
Set<CoachDto> coachDtos = CustomInternalCoachMapper.toDTOSet((Set<InternalCoachEntity>)coachEntities);
}
I get:
cannot cast from Set<CoachEntity> to Set<InternalCoachEntity>
Without cast it simply doesn't see mappers methods with subclass input.
The method toDTOSet(Set<InternalCoachEntity>) in the type CustomInternalCoachMapper is not applicable for the arguments (Set<CoachEntity>)
When in mapper I change method input to abstract Coach Class it doesn't see subclasses methods and fields.
Part of InternalMapper:
public class CustomInternalCoachMapper {
public static CoachDto toCoachDto(InternalCoachEntity coachEntity) {
if (coachEntity == null) {
return null;
}
EmployeeDto employeeDto = CustomEmployeeMapper.toEmployeeDto(coachEntity.getEmployeeEntity());
return new InternalCoachDto(coachEntity.getId(), coachEntity.getVersion(), coachEntity.getCreateDate(),
coachEntity.getUpdateDate(), coachEntity.getName(), coachEntity.getSurname(), employeeDto);
}
Is it possible to mapp this AbstractEntity Set into subclasses DTOs?
I also tried with AbstractDto for Coaches, but then I'm facing the same problem with no access to subclasses getters and setters.

BackendIdConverter not resolving to Entity

Overview
I've got an #Entity with an #EmbeddedId composite key. The entities are exposed over a REST api which uses a BackendIdConverter to convert the id string back to an instance of the #Embeddable composite key. My understanding is that this is then used to identify an instance of the #Entity, but that isn't happening.
Question
How is the #Embeddable composite key resolved back to an #Entity?
Code
#Entity
public class MyEntity {
#EmbeddedId
private MyEntityIdentifier id;
#Embeddable
public class MyEntityIdentifier implements Serializable {
public static final String COMPOSITE_KEY_DELIMITER = "_#_";
#Column
private String idPartOne;
#Column
#Temporal(TemporalType.DATE)
private Date idPartTwo;
#Component
public class StringToMyEntityIdentifierConverter implements BackendIdConverter {
#Override
public Serializable fromRequestId(String id, Class<?> aClass) {
String[] split = id.split(COMPOSITE_KEY_DELIMITER);
String idPartOne = split[0];
Date idPartTwo = Date.valueOf(split[1]);
return new MyEntityIdentifier(fullName, lastUpdated);
}
public interface MyEntityRepository extends JpaRepository<MyEntity, MyEntityIdentifier> {
}

#MappedSuperclass and implementation table

I inherited some pretty awful code that I am looking to refactor to make more reusable. There is a set of reporting tables which are primarily composed of 3 columns: id, report_type_fk, and report_description. I would like to merge all the reporting tables into one for ease of use.
I am refactoring the code and think that it would be better to break our current entities up so that Report is an abstract class with type implementations. For example a DmvReport extends Report, CreditScoreReport extends Report, etc.
The problem I am running into is that there would only be 1 report table that all entities would need to save to. Is there a way to make all concrete implementations of the abstract Report object save into the same table?
Here's an example of the bad code I inherited
Report class
#Entity
#Table(name = "report")
public class Report<E extends Exception> {
private long id;
private ReportType type;
private String description;
...
...
}
CreditReport class
#Entity
#Table(name = "credit_report")
public class CreditScore Report<E extends Exception> extends Report<E> {
private long id;
private ReportType type;
private String description;
...
...
}
I am looking to turn it into:
#MappedSuperclass
#Table(name = "report")
public abstract class Report<E extends Exception> {
#Id #Column(name="id")
private long id;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "report_type_id")
private ReportType type;
#column(name="description")
private String description;
...
...
}
#Entity
#Table(name = "report")
public class CreditScoreReport<E extends Exception> extends Report<E> {
public void doCreditScoreStuff(){
...
}
}
#Entity
#Table(name = "report")
public class DmvReport<E extends Exception> extends Report<E> {
public void doDmvStuff(){
...
}
}
I think you should use #Inheritance instead of #MappedSuperClass. Your code would look like this:
#Entity
#Table(name = "report")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "report_type_id", discriminatorType = DiscriminatorType.INTEGER)
public abstract class Report<E extends Exception> {
#Id #Column(name="id")
private long id;
#column(name="description")
private String description;
...
...
}
#Entity(name = "CreditScoreReport")
#DiscriminatorValue("1") // the id corresponding to the credit score report
public class CreditScoreReport<E extends Exception> extends Report<E> {
#Column(name = "specific_credit_score_report_1)
private Integer specificCreditScoreReport1;
public void doCreditScoreStuff(){
...
}
}
#Entity(name = "DmvReport")
#DiscriminatorValue("2") // the id corresponding to the DMV report
public class DmvReport<E extends Exception> extends Report<E> {
#Column(name = "specific_dmv_score_report_1)
private Integer specificDmvScoreReport1;
public void doDmvStuff(){
...
}
}
This strategy allows you to store credit score report and DMV report data in one table (report), but instanciate the proper entity according to the report_value_id field. You don't have to define the report_value_id in your parameters because it was already used to create the required entity.
Is this what you're looking for?

eclipselink condition on class type in ReportQuery

Assume I have two entities ChildClassA e ChildClassB that inherits from class MySuperClass:
#Entity
#Table(name = "MY_TAB")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "MY_DISC")
public class MySuperClass
{
#Column
private String X;
#Column
private String Y;
...
}
#Entity
#DiscriminatorValue(value = "A")
public class ChildClassA extends MySuperClass
{
...
}
#Entity
#DiscriminatorValue(value = "B")
public class ChildClassB extends MySuperClass
{
...
}
I also have another class OtherClass with a property myclass of type MySuperClass:
#Entity
public class OtherClass
{
...
#ManyToOne
private MySuperClass myclass;
...
}
I want to make a query on the entity OtherClass, in the where clause i need a condition like instanceof to apply on the property myclass.
Something like this:
ExpressionBuilder ebQuery = new ExpressionBuilder();
ReportQuery rQuery = new ReportQuery(OtherClass.class, ebQuery);
Expression exp = ebQuery.get("X").equal("my value x")
.and(ebQuery.get("Y").equal("my value y"))
.and(ebQuery.get("myClass").instanceOf(ChildClassA.class));
rQuery.setSelectionCriteria(exp);
ExecuteQuery(ebQuery);
Is there a way to do ebQuery.get("mySuperClass").instanceOf(ChildClassA.class) ?
You need to use type():
ebQuery.get("myClass").type().equal(ChildClassA.class)

QueryDSL query exception

I have a problem with a QueryDSL query. Classes:
#Entity
#Table(name="project")
#Cacheable(true)
#Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Project extends DomainObject implements Comparable<Project>, IconizedComponent, Commentable {
#ManyToMany(targetEntity=Student.class)
#JoinTable(name="project_student")
#Sort(type=SortType.NATURAL) //Required by hibernate
#QueryInit({"user"})
private SortedSet<Student> projectParticipants = new TreeSet<Student>();
private Project(){}
//attributes, get+set methods etc
}
#Entity
#Cacheable(true)
#Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) //Hibernate specific
public class Student extends Role {
public Student(){}
//attributes, get+set methods etc
}
#Entity
#DiscriminatorColumn(name = "rolename", discriminatorType = DiscriminatorType.STRING, length = 8)
#Table(name="role", uniqueConstraints={#UniqueConstraint(columnNames={"user_id","rolename"}, name = "role_is_unique")})
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public abstract class Role extends LazyDeletableDomainObject implements Comparable<Role> {
#ManyToOne(optional=false)
protected User user;
public Role(){}
//attributes, get+set methods etc
}
#Entity
#Table(name="user")
#Cacheable(true)
#Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) //Hibernate specific
public class User extends LazyDeletableDomainObject implements Comparable<User>, IconizedComponent {
private String firstName;
private String lastName;
public User(){}
//attributes, get+set methods etc
}
Query:
private BooleanExpression authorsNameContains(String searchTerm){
QUser user = new QUser("user");
user.firstName.containsIgnoreCase(searchTerm).or(user.lastName.contains(searchTerm));
QStudent student = new QStudent("student");
student.user.eq(user);
return QProject.project.projectParticipants.contains(student);
//java.lang.IllegalArgumentException: Undeclared path 'student'. Add this path as a source to the query to be able to reference it.
}
I have also tried annotating the projectParticipants set in Project with
#QueryInit("*.*")
But that gives the same exception. Any hints?
#Timo Westkämper
#siebZ0r
Thanks for your attention. Sorry for the delayed reply and incorrectly phrased question. Actually what I wanted to do was to write a working BooleanExpression.
In combination with the annotations already made, this was what I was after:
private BooleanExpression authorsFirstNameContains(String searchTerm){
return QProject.project.projectParticipants.any().user.firstName.containsIgnoreCase(searchTerm);
}
I got this right with the help of a colleague.

Categories

Resources