1

please note: I am new to play framework

Info:

I have been searching for a couple of hours for a decent tutorial or explanation about inheritance using Play Framework,

yet each example seems to omit the entirety of the example, which is causing the confusion, hence the reason for this question.

For the record, I am using MariaDB (aka ~MySQL)

Research & documentation:

  • Documentation- Play: this was not very helpful

  • Documentation - Java: useful, lacked complete example

  • Stackoverflow: a few questions, here, and here

  • Blogs: This lacks complete example, although insightful

  • Search on Youtube, useful for setting up eBean in PlayFramework.

However, I may just be misunderstandin completely, but the examples provided join columns with different names to a table unmentioned.

Problem/Question:

I need to create a database utilising inheritance (i.e. foreign keys, etc.), how exactly, with explanations, can I do that?

Database Schema:

enter image description here

What I have currently:

Model: User

import io.ebean.Finder;
import io.ebean.Model;
import play.data.validation.Constraints;

import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

@MappedSuperclass
//@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class User extends Model {

    @Id
    @GeneratedValue
    private Long userId;

    @Constraints.MaxLength(50)
    private String name;
    @Constraints.MaxLength(100)
    private String surname;

    @Constraints.Required
    @Constraints.MinLength(8)
    private String password;

    @Constraints.Email
    @Constraints.Required
    private String email;

    @Constraints.Required
    @Constraints.MaxLength(10)
    @Constraints.MinLength(10)
    @Constraints.Pattern("[0]\\d{2}[- ]{0,1}\\d{3}[- ]{0,1}\\d{4}")
    private String cellNumber;

    private Boolean emailVerified = false;
    private String token;

    public static Finder<String, User> find = new Finder<String, User>(User.class);

    public User(){}

    public User(@Constraints.MinLength(10) @Constraints.MaxLength(10) Long userId, String name, String surname, @Constraints.Required String password, @Constraints.Email @Constraints.Required String email, @Constraints.Required @Constraints.Pattern("[0]\\d{2}[- ]{0,1}\\d{3}[- ]{0,1}\\d{4}") String cellNumber, Boolean emailVerified) {
        this.userId = userId;
        this.name = name;
        this.surname = surname;
        this.password = password;
        this.email = email;
        this.cellNumber = cellNumber;
        this.emailVerified = emailVerified;
    }

Model: Staff

import io.ebean.Finder;
import play.data.validation.Constraints;

import javax.persistence.Entity;
import javax.persistence.Id;

/**
 * Created by cybex on 2017/07/13.
 */

@Entity
public class Staff extends User {

    @Id
    private Long userId;

    @Constraints.Required
    private Boolean isKitchenStaff;

    public static Finder<String, Staff> find = new Finder<String, Staff>(Staff.class);

    public Staff(@Constraints.Required Long userId, String name, String surname, @Constraints.Required String password, @Constraints.Email @Constraints.Required String email, @Constraints.Required @Constraints.Pattern("[0]\\d{2}[- ]{0,1}\\d{3}[- ]{0,1}\\d{4}") String cellNumber, String userId1, @Constraints.Required Boolean isKitchenStaff) {
        super(userId, name, surname, password, email, cellNumber, false);
        this.isKitchenStaff = isKitchenStaff;
    }
}

Model: Customer

import io.ebean.Finder;
import play.data.validation.Constraints;
import javax.persistence.Entity;

@Entity
public class Customer extends User {

    @Constraints.Required
    private Address address;
    private Boolean isStudent = false;

    public Customer(){}

    public Customer(Long userId, String name, String surname, String email, String cellNumber, String password, String userId1, @Constraints.Required Address address, Boolean isStudent) {
        /*super(userId, name, surname, email, cellNumber, password, false);*/
        this.address = address;
        this.isStudent = isStudent;
    }

    public static final Finder<String, Customer> find = new Finder<String, Customer>(Customer.class);
}

Model: Address

import io.ebean.Finder;
import io.ebean.Model;
import play.data.validation.Constraints;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

/**
 * Created by cybex on 2017/07/13.
 */

// TODO: 2017/07/13 Add fields for suburb and city for larger projects
@Entity
public class Address extends Model {

    @Id
    @Constraints.Required
    @GeneratedValue
    @Constraints.Max(15)
    private Long addressId;
    @Constraints.Required
    private String unitNumber;
    @Constraints.Required
    private String streetName;
    private String communityName;
    private Boolean isCommunity;

    public Address(@Constraints.Required Long addressId, @Constraints.Required String unitNumber, @Constraints.Required String streetName, String communityName, Boolean isCommunity) {
        this.addressId = addressId;
        this.unitNumber = unitNumber;
        this.streetName = streetName;
        this.communityName = communityName;
        this.isCommunity = isCommunity;
    }

    public static Finder<String, Address> find = new Finder<String, Address>(Address.class);
}

Suggested was to add:

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

however it results in an error:

CreationException: Unable to create injector, see the following errors:

1) Error injecting constructor, java.lang.NullPointerException

see here for full stack trace

Question(and TL;DR):

TL;DR: A basic fully functional, with explainations, Play! Framework models representing the database schema shown above.

CybeX
  • 2,060
  • 3
  • 48
  • 115

1 Answers1

2

Inheritance Types Not Supported (as of Aug 18, 2017)

Play uses Ebean ORM, which currently does not support InheritanceType.TABLE_PER_CLASS or InheritanceType.JOINED. It is an outstanding issue listed here.

I have verified this. I copied your code and ran it with InheritanceType.TABLE_PER_CLASS. Play created the User and Address tables, but not the child tables, Staff and Customer.

For reference, to get rid of the error...

Error injecting constructor, java.lang.NullPointerException

...you need to add the @Entity annotation to User, along with the InheritanceType:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class User extends Model {
...

For the sake of completion, you would also need to remove...

private Long userId;

...from Staff since it is inherited from User.


What are your options?

1) You can restructure your project to use hibernate with Play, which supports InheritanceType.TABLE_PER_CLASS and InheritanceType.JOINED.

2) Use InheritanceType.SINGLE_TABLE. This method will store User, Customer, and Staff data in one table, User. It may not be your ideal implementation, but it will work:

If you decide on this, please note...

The single table strategy maps all entities of the inheritance structure to the same database table. This approach makes polymorphic queries very efficient and provides the best performance.

But it also has some drawbacks. The attributes of all entities are mapped to the same database table. Each record uses only a subset of the available columns and sets the rest of them to null. You can, therefore, not use not null constraints on any column that isn’t mapped to all entities. That can create data integrity issues, and your database administrator might not be too happy about it. more on Inheritance Type comparisons

Model: User

import javax.persistence.DiscriminatorColumn;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;

import com.avaje.ebean.Model;

import play.data.validation.Constraints;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "user_type")
public class User extends Model {

  @Id
  @GeneratedValue
  private Long userId;

  @Constraints.MaxLength(50)
  private String name;
  @Constraints.MaxLength(100)
  private String surname;

  @Constraints.Required
  @Constraints.MinLength(8)
  private String password;

  @Constraints.Email
  @Constraints.Required
  private String email;

  @Constraints.Required
  @Constraints.MaxLength(10)
  @Constraints.MinLength(10)
  @Constraints.Pattern("[0]\\d{2}[- ]{0,1}\\d{3}[- ]{0,1}\\d{4}")
  private String cellNumber;

  private Boolean emailVerified = false;
  private String token;

  public static Finder<String, User> find = new Finder<String, User>(User.class);

  public User(){}

}

@DiscriminatorColumn(name = "user_type") is how you distinguish a record. user_type value will be null for a User record, customer for a Customer record, and staff for a Staff record.

Model: Staff

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

import play.data.validation.Constraints;

@Entity
@DiscriminatorValue("staff")
public class Staff extends User {

    @Constraints.Required
    private Boolean isKitchenStaff;

    public static Finder<String, Staff> find = new Finder<String, Staff>(Staff.class);

    public Staff() {}
}

Model Customer:

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.OneToOne;

import play.data.validation.Constraints;

@Entity
@DiscriminatorValue("customer")
public class Customer extends User {

    @Constraints.Required
    @OneToOne(mappedBy = "customer")
    private Address address;
    private Boolean isStudent = false;

    public Customer(){}

    public static final Finder<String, Customer> find = new Finder<String, Customer>(Customer.class);
}

@OneToOne address mapping added to Customer.

Model Address:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;

import com.avaje.ebean.Model;

import play.data.validation.Constraints;

@Entity
public class Address extends Model {

    @Id
    @Constraints.Required
    @GeneratedValue
    @Constraints.Max(15)
    private Long addressId;
    @Constraints.Required
    private String unitNumber;
    @Constraints.Required
    private String streetName;
    private String communityName;
    private Boolean isCommunity;

    @OneToOne
    @JoinColumn(name = "user_id")
    private Customer customer;

    public Address(){}

    public static Finder<String, Address> find = new Finder<String, Address>(Address.class);
}

@OneToOne customer mapping added to Address.

This is verified using Play 2.5.10.

agentcurry
  • 2,405
  • 3
  • 17
  • 21