Ebean #ManyToOne, finder doesn't retrieve all data of related object - java

I'm using Ebean for my object-mapping and I've made my SQL tables like so
create table company (
id int auto_increment not null primary key,
name varchar(100)
);
create table employee (
id int auto_increment not null primary key,
name varchar(100) not null,
company_id int not null,
constraint foreign key (company_id) references company (id)
on delete restrict on update restrict
);
This is the company Ebean model
import javax.persistence.Entity;
import javax.persistence.Id;
import com.avaje.ebean.Model;
#Entity
public class Company extends Model
{
#Id
public Integer id;
public String name;
}
And employee model
#Entity
public class Employee extends Model
{
#Id
public Integer id;
public String name;
#ManyToOne
public Company company;
public static Finder<Long, Employee> find = new Finder<Long, Employee>(Long.class, Employee.class);
}
When I run the following
Company company = new Company();
company.name = "Microsoft";
company.save();
Employee employee = new Employee();
employee.name = "Mr John";
employee.company = company;
employee.save();
Employee mrJohn = Employee.find.where().eq("id", 1).findUnique();
System.out.println(mrJohn.company.id);
System.out.println(mrJohn.company.name);
The first System.out.println gives 1 (which is the correct id of the company assigned to the employee) but the second shows null (which I expected should have the value "Microsoft"), the output being
1
null
The question therefore is why is only the id of the Company model retrieved, and not the other associated data?

You can use fetch() to eagerly fetch other parts of the graph. In this case fetch the company name like:
Employee.find.fetch("company","name").where().eq("id",1).findUnique();
In short field access can not be intercepted (unless you enhance the caller). So using field access for company.name meant that customer.name was a GETFIELD operation and that it was not being intercepted by Ebean and hence lazy loading was not invoked (and hence a null was returned).
Changing over to use getters/setters meant that lazy loading was invoked when customer.getName() was called.
Java does not support properties (hance the getters and setters). You can look at other JVM languages that do like Groovy and Kotlin.
Groovy supports properties, an example using Groovy with #CompileStatic is:
https://github.com/ebean-orm/avaje-ebeanorm-examples/blob/master/e-groovy
https://github.com/ebean-orm/avaje-ebeanorm-examples/blob/master/e-groovy/src/main/groovy/org/example/domain/Customer.groovy
Kotlin supports properties, an example is:
https://github.com/ebean-orm/avaje-ebeanorm-examples/tree/master/e-kotlin-maven
Cheers, Rob.

I faced this problem today and I found out the reason it's fetch lazily didn't invoke when we access public variable.
Fetch lazily can be invoked by calling getter method of attributes. So I need to create getter in my model fetch data using getter method.
#Entity
public class Company extends Model
{
#Id
public Integer id;
public String name;
public Integer getId() {
return id;
}
public String getName() {
return name;
}
}
Then fetch company:
System.out.println(mrJohn.company.getName());

Related

In OOP, should I define different objects for writing to and reading from a table?

How should I return the Primary Key of my table-read API requests without requiring a Primary Key on my table-write API requests? They use the same object.
I have a Person class and two controller functions.
public class Person {
private Integer id; // this is the primary key in my table
private String name;
private Integer houseId;
private Integer age;
}
#RestController
public class TestController {
#RequestMapping("/")
public List<Person> fetchAll() { // this function does a "SELECT *" on all rows
return personDao.findAll();
}
#PostMapping(value = "/createPerson")
public void createPerson(#RequestBody Person person) { // this function does an "INSERT"
personDao.insertPerson(person);
}
}
I don't want to require the table Primary Key (class attribute "id") on all my "createPerson" API requests, since the client won't know it. I want to skip on that value, and let the table increment the PK on insert.
However, I want to return the "id" in the "fetchAll" API requests. And I want it defined on the Person object.
I don't see a way to do this besides making "id" Optional type, or defining two separate objects: "PersonRequest" and "PersonResponse", both which will extend "Person" class. Both of which seems like a bad practice
Is there something I'm missing?
Since you said that you want to have the id automatically incremented make sure to add the annotations below to your id field:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
There is nothing stopping you from saving an object without an id even though it has a property id. If you do:
Person person = new Person(); //person is empty here
person = personRepository.save(person);
The return value assigned to person will have an id. Also when you execute:
Person person = personRepository.findById(id);
The person object will have the id, I am not sure what else is confusing you.

Implicit polymorphism vs Explicit polymorphism in Hibernate

I have read the O/R Mapping of Hibernate and I just can't seem to get past the part on polymorphism.
According to https://docs.jboss.org/hibernate/orm/5.0/manual/en-US/html/ch05.html,
Implicit polymorphisms means that instances of the class will be
returned by a query that names any superclass or implemented interface
or class, and that instances of any subclass of the class will be
returned by a query that names the class itself
whereas
Explicit polymorphisms means that class instances will be returned
only by queries that explicitly name that class. Queries that name the
class will return only instances of subclasses mapped
I just want to understand how these 2 work. Can somebody explain these terms using an example(doesn't have to be too complex) with the use of code? I would appreciate your help
First of all the org.hibernate.annotations.Entity annotation is deprecated now. You should use the #Polymorphism annotation instead.
Now, imagine that you have the following schema:
create table TST_STUDENT
(
st_id int not null,
st_name varchar(200),
primary key (st_id)
);
insert into TST_STUDENT values (1, 'Kostya'), (2, 'Yulia'), (3, 'Borya'), (4, 'Misha');
create table TST_TEACHER
(
tcr_id int not null,
tcr_first_name varchar(200),
tcr_last_name varchar(200),
primary key (tcr_id)
);
insert into TST_TEACHER values (1, 'Mikhail', 'Bulgakov'), (2, 'Leo', 'Tolstoy');
and the following mapping:
public interface Person
{
Long getId();
String getName();
}
#Entity
#Table(name = "TST_STUDENT")
public class Student implements Person
{
#Id
#Column(name = "st_id")
private Long id;
#Column(name = "st_name")
private String name;
public Student()
{
}
// getters / setters
}
and Teacher entity:
import org.hibernate.annotations.Polymorphism;
import org.hibernate.annotations.PolymorphismType;
#Entity
#Table(name = "TST_TEACHER")
// #Polymorphism(type = PolymorphismType.EXPLICIT)
public class Teacher implements Person
{
#Id
#Column(name = "tcr_id")
private Long id;
#Column(name = "tcr_first_name")
private String name;
#Column(name = "tcr_last_name")
private String lastName;
public Teacher()
{
}
// getters / setters
}
Now, if you run the following query:
List<Person> persons = em.createQuery("select p from com.your.entities.Person p", Person.class).getResultList();
you will get all rows from the TST_STUDENT table plus all rows from the TST_TEACHER table.
But, if you uncomment this line:
#Entity
#Table(name = "TST_TEACHER")
#Polymorphism(type = PolymorphismType.EXPLICIT) // now we use explicit polymorphism for the Teacher entity
public class Teacher implements Person
The mentioned above query will return only rows from the TST_STUDENT table.
This is what this annotation mean.
By default, when you query a base class entity, the polymorphic query will fetch all subclasses belonging to the base type. You can even query interfaces or base classes that don’t belong to the JPA entity inheritance model.
P.S. See also this part of documentation.

ORMLite android class field is id of other class

How to implement relationship between 2 classes using ORMLite? I know about foreign field, but i can't add non-string Department field to product class
Class Product
#DatabaseTable(tableName = "PRODUCTS")
public class Product {
#DatabaseField(id = true)
private String id;
#DatabaseField()
private String name;
#DatabaseField() //This field is id of Department class
private String department;
Department class
#DatabaseTable(tableName = "DEPARTMENTS")
public class Department {
#DatabaseField(id = true)
private String id;
#DatabaseField()
private String name;
How to implement relationship between 2 classes using ORMLite? I know about foreign field, but i can't add non-string Department field to product class
RTFM please. Did you look at any of the documentation or examples? Here's the docs on foreign objects. You can see plainly that you put a Department into Product not a String. The docs show the Order object having an Account field.
#DatabaseField
private Department department;
Behind the scenes, what ORMLite does is actually store just the id from the department into your Product. If you actually look at the schema then you will see a string there but you don't do that yourself.
Then when you retrieve your Product, it will have a Department field but only the id will be filled out.
There is also a foreign object example in the code that may also help.

JPA OneToOne relationship

I am building a project using the Play framework and I am having trouble getting my head around JPA #OneToOne relationships.
I currently have two classes:
User Object
#Entity
#Table( name="users" )
public class Users extends Model {
#OneToOne( mappedBy="userId", fetch=FetchType.LAZY, cascade = CascadeType.ALL )
#ForeignKey( name="userId", inverseName="userId" )
UserSettings userSettings;
public userId;
public userName;
}
UserSettings
#Entity
#Table( name="user_settings" )
public class UserSettings extends Model {
#OneToOne( cascade = CascadeType.ALL,targetEntity=User.class )
public String userId;
public String xml;
public UserSettings( String userId ){
this.userId = userId;
}
}
The idea is that I am trying to set the userId field within User as a foreign key within UserSettings. I have tried a few different ways to achieve this and my code always throws an error. The most common error I recveive is:
Referenced property not a (One|Many)ToOne.
However, When I try to set the userId in UserSettings using the code above, I receive the following exception:
A javax.persistence.PersistenceException has been caught, org.hibernate.PropertyAccessException: could not get a field value by reflection getter of reader.User.id
Can anybody help explain how I can achieve my desired goal?
Read section 5.2 of the hibernate reference about the difference between entities and values. You're trying to map a String as an entity. Only entities can be a (One|Many)ToOne, as the error is telling you. I.e., instead of String userId, you should be using User user, and instead of mappedBy="userId", mappedBy="user".
If you extend Model, Play will generate an primary key "id" by default for each entity. If this is not what you want you should extend Generic model instead.
The simplest way would be to have user as a property of user settings:
#Entity
#Table( name="user_settings" )
public class UserSettings extends Model{
#OneToOne
public Users user;
...
#Entity
#Table( name="users" )
public class Users extends Model {
#OneToOne(cascade = CascadeType.ALL)
public UserSettings settings;
Maintaining User and UserSettings in each entity with OneToOne allows you to have bi-directional searches.
If you want to use your own key change from Model to GenericModel and defined your foreign key on the user object.

Hibernate mapping with two tables

I am trying to learn Hibernate and I could create some simple CRUD operation using a Single Class and Single Table. I am just reading the Hibernate Doc and some online tutorial.
But I have a problem on how to define this relationship with two tables involved. I basically have an Employee table with this structure.
CREATE TABLE EMPLOYEE
(
EMP_ID VARCHAR(10) NOT NULL,
EMP_FIRST_NAME VARCHAR(30) NOT NULL,
EMP_LAST_NAME VARCHAR(30) NOT NULL,
STATUS_ID INT NOT NULL,
PRIMARY KEY (EMP_ID)
);
The STATUS_ID field references another table. STATUS_DESC can either be 'PERMANENT', 'CONTRACTUAL', 'ON-DEMAND'
CREATE TABLE EMP_STATUS
(
STATUS_ID VARCHAR(10) NOT NULL,
STATUS_DESC VARCHAR(100) ,
PRIMARY KEY (STATUS_ID)
);
I am thinking of having an Entity class like this. Now my goal is to return list of Employee object with status, but I don't know how to go about on doing this.
#Entity
public class Employee{
//other private instance
private EmployeeStatus empStatus;
//getters and setters.
}
public class EmployeeStatus{
private int statusID;
private String statusDesc;
//getters and setters
}
You want to know how to map it? ManyToOne?
Employee.java
#Entity
public class Employee{
//other private instance
#JoinColumn(name = "empStatus", referencedColumnName = "yourColName")
#ManyToOne(optional = false)
private EmployeeStatus empStatus;
//getters and setters.
}
Dont forget to change "referencedColumnName" value...
EmployeeStatus.java
#Entity
public class EmployeeStatus{
#Id //this is your pk?
private int statusID;
private String statusDesc;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "empStatus", fetch = FetchType.LAZY) //or EAGER
private List<Employee> empList;
//getters and setters
}
To create a relationship between two tables you need to decide:
Is the relationship bi-directional? That is, do the statuses know the employees or not? If no then it is uni-directional. In that case you can add the annotation on the Employee class like this:
#ManyToOne
#JoinColumn(name = "status")
private EmployeeStatus empStatus;
And there is a few other options that you may add.
You can do what you are doing, but I would suggest, if the status can only be one of three values, create an Enum with the three values. No need for a separate table.
The downside for this is you need to create a hibernate custom type (the code is on the wiki) to support persisting enums.
A simpler answer is to not use a secondary table, and just save the status as a String on the domain object. You can put business logic on your model to ensure the String is in the list of acceptable values.
If you really want to use a relationship between two entities, then check out the hibernate docs on many-to-one relationships.
You can use HQL to query the entities. Like so
Query q = s.createQuery("from Employee as e where e.empStatus = :status");
q.setParameter("status", status);
List emps= q.list();

Categories

Resources