I have seen examples of setting up Map association with EBean. Examples I've seen typically support a schema as below.
With such a schema, the JPA mapping of Video would have a property public List<VideoMetadata> metadata;.
How can I change this so that metadata is a Map instead of a List, for instance public Map<String, String> metadata;?
CREATE TABLE video (
id UUID PRIMARY KEY,
title VARCHAR(255) NOT NULL
);
CREATE TABLE metadata (
id BIGSERIAL PRIMARY KEY,
video_id UUID NOT NULL, -- this relationship is not polymorphic
key VARCHAR(255) NOT NULL,
value VARCHAR(255) NOT NULL
);
I have defined a polymorphic relationship between Metadata and Video (and Photo, and on...). A Video can have multiple metadata, as can a Photo.
The polymorphic relationship is configured with InheritanceType.SINGLE_TABLE and the "describable" is identified via a combination of describable_type and describable_id.
The database schema looks like this.
CREATE TABLE video (
id UUID PRIMARY KEY,
title VARCHAR(255) NOT NULL
);
CREATE TABLE metadata (
id BIGSERIAL PRIMARY KEY,
describable_id UUID NOT NULL, -- now we have polymorphism
describable_type VARCHAR(255) NOT NULL, -- now we have polymorphism
key VARCHAR(255) NOT NULL,
value VARCHAR(255) NOT NULL
);
And the models (in case it's relevant, I'm using Ebean)...
package models;
import io.ebean.Model;
import javax.persistence.Id;
import java.util.UUID;
import play.data.validation.Constraints;
import javax.persistence.*;
import java.util.List;
#Entity
public class Video extends Model {
#Id
public UUID id;
#Constraints.Required
public String title;
#ElementCollection(fetch = FetchType.EAGER)
#OneToMany(mappedBy = "describable", cascade = CascadeType.ALL)
public List<VideoMetadata> metadata;
}
package models;
import io.ebean.Model;
import play.data.validation.Constraints;
import javax.persistence.*;
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
name = "describable_type",
discriminatorType = DiscriminatorType.STRING
)
public abstract class Metadata extends Model {
#Id
#GeneratedValue
public Long id;
#Constraints.Required
public String key;
#Constraints.Required
public String value;
}
package models;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
#Entity
#DiscriminatorValue("video")
public class VideoMetadata extends Metadata {
#ManyToOne(optional = false)
public Video describable;
}
package models;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
#Entity
#DiscriminatorValue("photo")
public class ImageMetadata extends Metadata {
#ManyToOne(optional = false)
public Photo describable;
}
Related
Description
In a Spring Boot 1.5.9 application,
I am using org.springframework.data.mybatis.annotations.Version annotation within an #Entity on version attribute to get a version incremented on each update, but it keep breaking my application.
The error is:
update effect 0 row, maybe version control lock occurred.
I can see that the request end with:
"version"="version"+1,
where
"id"=21
and "version"=null
and "version"=null cause the whole request to edit 0 row.
This is an example of entity:
CREATE TABLE "cm_company_postal_address"
(
"id" BIGSERIAL NOT NULL,
"note" VARCHAR(255) DEFAULT NULL,
"city" VARCHAR(255) NOT NULL,
"postal_code" BIGINT DEFAULT NULL,
"c_ref_country_id" BIGINT NOT NULL,
"cm_company_id" BIGINT NOT NULL,
"street_address_line1" VARCHAR NOT NULL,
"street_address_line2" VARCHAR DEFAULT NULL,
"post_box" VARCHAR(20) DEFAULT NULL,
"version" BIGINT DEFAULT NULL,
"created_date_time" TIMESTAMPTZ DEFAULT NULL,
"created_by_id" BIGINT DEFAULT NULL,
"last_modified_date_time" TIMESTAMPTZ DEFAULT NULL,
"last_modified_by_id" BIGINT DEFAULT NULL,
"deleted" BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id"),
CONSTRAINT company_id_fk FOREIGN KEY ("cm_company_id") REFERENCES "cm_company" ("id")
);
This is the java class:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.data.mybatis.annotations.Column;
import org.springframework.data.mybatis.annotations.Condition;
import org.springframework.data.mybatis.annotations.Conditions;
import org.springframework.data.mybatis.annotations.Entity;
import static org.springframework.data.repository.query.parser.Part.Type.CONTAINING;
#Data
#JsonIgnoreProperties("new")
#Entity(table = "cm_company_postal_address")
#EqualsAndHashCode(callSuper = false)
public class PostalAddress {
#Id(strategy = AUTO)
private Long id;
private String description;
#Version
private Integer version;
#CreatedDate
#JsonUnwrapped
#JdbcType(TIMESTAMP)
private Instant createdDateTime;
#LastModifiedDate
#JsonUnwrapped
#JdbcType(TIMESTAMP)
private Instant lastModifiedDateTime;
#CreatedBy
private Long createdById;
#LastModifiedBy
private Long lastModifiedById;
#JsonProperty(value = "deleted")
private Boolean deleted = false;
#Column(name = "cm_company_id")
private Long companyId;
private String city;
private Long postalCode;
private Long countryId;
private String streetAddressLine1;
private String streetAddressLine2;
private String postBox;
}
This is my #Repository for the presented entity:
import com.kopaxgroup.api.companyManagement.domain.postalAddress.geography.PostalAddress;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.data.mybatis.repository.support.MybatisRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
#RepositoryRestResource(exported = false)
public interface PostalAddressRepository extends MybatisRepository<PostalAddress, Long> {
}
This is the service interface for the same entity:
import com.kopaxgroup.api.companyManagement.domain.postalAddress.geography.PostalAddress;
import org.springframework.data.support.CrudService;
public interface PostalAddressService extends CrudService<PostalAddress, Long> {
}
And it's implementation:
import com.kopaxgroup.api.companyManagement.domain.postalAddress.geography.PostalAddress;
import com.kopaxgroup.api.companyManagement.repository.PostalAddressRepository;
import com.kopaxgroup.api.companyManagement.service.PostalAddressService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.support.AbstractCrudService;
import org.springframework.stereotype.Service;
#Service
public class PostalAddressServiceImpl extends AbstractCrudService<PostalAddressRepository, PostalAddress, Long> implements PostalAddressService {
#Autowired
public PostalAddressServiceImpl(PostalAddressRepository repository) {
super(repository);
}
And this is how I update the entity:
import com.kopaxgroup.api.companyManagement.domain.postalAddress.geography.PostalAddress;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping("/postal-address")
public class PostalAddressController {
#Autowired
private PostalAddressService postalAddressService;
#PutMapping("/{id}")
#ResponseStatus(HttpStatus.NO_CONTENT)
void modify(#PathVariable("id") Long id, #RequestBody PostalAddress entity) {
entity.setId(id);
postalAddressService.updateIgnore(entity);
}
}
Expected
I expect to have the version column of this entity to be incremented on each update.
Result
Instead, each update keep a null value for version column.
Question
How can I prevent spring-data-mybatis from appending and "version"=null at the end of the request?
Version
spring boot: 1.5.9.RELEASE
spring-boot-starter-data-mybatis: 1.0.17.RELEASE
Spring-data has also its own version annotation: org.springframework.data.annotation.Version. This annotation is used in spring-data-mybatis lib.
First let's say we have two tables. One table is an Employee table with the following columns:
EMPLOYEE:
------------------------
emp_id (int, primary key)
emp_name (varchar(125))
emp_dept (foreign key)
emp_intro (text)
The other table is a Department table with the following columns:
DEPARTMENT:
-----------
dept_id (int, primary key)
dept_label (varchar(25))
Here is a sample of the table's values
DEPARTMENT:
------------------------
dept_id | dept_label
------------------------
1 | Sales
------------------------
2 | Technology
------------------------
3 | Finance
In order to return the employee's info with a status label, we need to either perform a JOIN:
SELECT e, d.dept_label FROM employees JOIN department d ON d.dept_id = e.emp_dept
or a multi-table select:
SELECT e.emp_id, e.emp_name, d.dept_label, e.emp_intro FROM employees e, department d WHERE e.emp_dept = d.dept_id
However, when using JPA/Hibernate, we need to create two classes:
Employee.java
package com.example.entities;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "employees")
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "emp_id")
private long emp_id;
#Column(name = "emp_name")
private String emp_name;
#Column(name = "emp_dept")
private Integer emp_dept;
#Column(name = "emp_intro")
private String emp_intro;
public long getEmp_id() {
return emp_id;
}
public void setEmp_id(long emp_id) {
this.emp_id = emp_id;
}
public String getEmp_name() {
return emp_name;
}
public void setEmp_name(String emp_name) {
this.emp_name = emp_name;
}
public Integer getEmp_dept() {
return emp_dept;
}
public void setEmp_dept(Integer emp_dept) {
this.emp_dept = emp_dept;
}
public String getEmp_intro() {
return emp_intro;
}
public void setEmp_intro(String emp_intro) {
this.emp_intro = emp_intro;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
}
Department.java
package com.example.entities;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "departments")
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "dept_id")
private long dept_id;
#Column(name = "dept_label")
private String dept_label;
public long getDept_id() {
return dept_id;
}
public void setDept_id(long dept_id) {
this.dept_id = dept_id;
}
public String getDept_label() {
return dept_label;
}
public void setDept_label(String dept_label) {
this.dept_label = dept_label;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
}
Then, there is the repository (DAO):
EmployeeRepository
package com.example.repository;
import.java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.example.entities.Employee;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
#Query("select e, d.dept_label FROM Employee e JOIN Department d ON "
+ "d.dept_id = e.emp_id")
public List<Employee> return getEmployees();
}
and lastly, the Java controller that binds the classed query to an endpoint of the application:
EmployeeController.java
package com.example.controllers;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.entities.Department;
import com.example.entities.Employee;
import com.example.repository.EmployeeRepository;
#Controller
public class EmployeeController {
#Autowired
EmployeeRepository er;
#RequestMapping(value = "/getEmployees")
public #ResponseBody List<Employee> getEmployees() {
return er.getEmployees();
}
}
I have already tested this entire structure with only retrieving rows inside of the Employee table (i.e. #Query("SELECT e FROM Employee e") ) and everything returns as is.
MY MAIN ISSUE is how does one return a JOIN QUERY while the query is inside of a specific class (table), being Employee, if I require contents inside of Department?
I've already tried #JoinColumn annotations and that didn't work as well (perhaps I did it wrong).
Any ideas? Thanks.
You dont have to use raw joins to do that, just use proper relation mapping. Relation between Employee and Departament sounds like #ManyToOne or #ManyToMany.
You will be able to eg employee.getDepartament() or query by employee.departament.name=:name
http://www.objectdb.com/api/java/jpa/ManyToMany
You can even map bidirectional relations so you will be able to get deparament from employee, as well as all employees from given deparaments
PS. #JoinColumn is used to delare DB columnt used for joins it it is different then created by selected named strategies (usualy entityname_id). Actual relation mapping is done by declaring #OneToOne #OneToMany #ManyToMany and those can but doesn't have to be used with #JoinColumn. It is strict JPA question.
Here you have complete documentation of JPA 2.1 specification
It describes in details how to declare relations as well as #MappedSuperclass, inheritance strategies and all other usefull stuff.
I have a MySQL table with UUID as id. When I select (in MySQL Workbench), I get:
269ecb9e-b7fe-11e6-a630-657416131b50
667d6caa-b800-11e6-a630-657416131b50
6f94b93c-b734-11e6-8618-ba2ba7c3db03
ab8478b8-b7fe-11e6-a630-657416131b50
ddb6b7d8-b80d-11e6-a630-657416131b50
fc031a90-b805-11e6-a630-657416131b50
Then I query in Java using Hibernate, and I get:
32363965-6362-3965-2d62-3766652d3131
36363764-3663-6161-2d62-3830302d3131
36663934-6239-3363-2d62-3733342d3131
61623834-3738-6238-2d62-3766652d3131
64646236-6237-6438-2d62-3830642d3131
66633033-3161-3930-2d62-3830352d3131
As you can see, I really don't get the same result.
My database schema uses a char(36) and is as:
CREATE TABLE `table` (
`id` char(36) NOT NULL COMMENT 'Unique identifier as a UUID.',
`fragment` text COMMENT 'Generic field. '
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
My Hibernate POJO is:
package net.jgp.map.pojo;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.UUID;
#Entity
#Table(name = "table", catalog = "z_map")
public class ExternalMapping {
private String id;
private String fragment;
#Id
#Column(name = "id", unique = true, nullable = false)
public UUID getId() {
return java.util.UUID.fromString(this.id);
}
public void setId(UUID id) {
this.id = id.toString();
}
#Column(name = "fragment")
public String getFragment() {
return this.fragment;
}
public void setFragment(String fragment) {
this.fragment = fragment;
}
}
The rest is classic Hibernate code (happy to share if helpful).
My first attempt was without the toString() stuff and using UUID natively in the POJO code, but the result was the same.
Person.java
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
#Entity
#Table(name="person")
public class Person {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name="name")
private String name;
#Column(name="age")
private int age;
#OneToOne(mappedBy="person")
private Passport passport;
----getter and setter----
Passport.java
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
#Entity
public class Passport {
#Id
private String passportNumber;
private Date issuedDate;
private Date expiryDate;
private String issuedPlace;
private String nationality;
#OneToOne
#Cascade(value={CascadeType.DELETE})
#JoinColumn(name="frn_person_id", unique=true, referencedColumnName="id")
private Person person;
---getter setter----
Hibernate Version: 4.2.0.Final
MySQL : 5.6.17
The above code generates SQL table like below:
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`age` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
CREATE TABLE `passport` (
`passportNumber` varchar(255) NOT NULL,
`expiryDate` datetime DEFAULT NULL,
`issuedDate` datetime DEFAULT NULL,
`issuedPlace` varchar(255) DEFAULT NULL,
`nationality` varchar(255) DEFAULT NULL,
`frn_person_id` int(11) DEFAULT NULL,
PRIMARY KEY (`passportNumber`),
KEY `FK4C60F032382EC083` (`frn_person_id`),
CONSTRAINT `FK4C60F032382EC083` FOREIGN KEY (`frn_person_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
However, I want to generate tables like below:
create table jaydb.person(
id integer auto_increment primary key,
name varchar(100) unique,
age integer
);
create table jaydb.passport(
passport_number varchar(50) primary key,
issued_date date,
expiry_date date,
issued_place varchar(100),
nationality varchar(100),
frn_person_id integer unique,
foreign key(p_id)
references jaydb.person(p_id)
on delete cascade
on update cascade
);
As you can see the cascade code is missing from the hibernate's generated tables. My requirement is to setup One-to-One mapping between Person and Passport. If person data gets deleted then it's associated passport should be deleted as well.
I've tried javax.persistence cascade as well but it didn't work.
I believe that in your schema, the Person class is the owning relationship, as evidenced by that you have the mappedBy property on the passport which the person owns. As far as I know, the cascade annotation should be on the owning side of the relationship. However, you currently have it on the passport. Change your Person class to look like this:
#Entity
#Table(name="person")
public class Person {
...
#OneToOne(mappedBy="person")
#Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
private Passport passport;
...
}
And remove the #Cascade annotatiom from the Passport class.
I have a class that uses a Hibernate session to call a stored procedure via a JPA #NamedNativeQuery using an in memory H2 database for testing (the actual database is MySQL). The stored procedure inserts a record into a database table and then returns that row.
However during testing, on converting to the JPA #Entity, I am seeing an H2 database error: org.h2.jdbc.JdbcSQLException: Column "Id" not found.
I've documented a cut down version of the code below.
As far as I can tell I think it has something to do with the H2 interpretation of the #Id annotation, but don't understand why, so any help would be gratefully appreciated...
NB - I have searched Stack overflow fairly extensively, including the issue relating to the use of double quotes for column specification, but don't think that this relates to my situation...
Table
CREATE TABLE History.Status_Report (
Id INT NOT NULL AUTO_INCREMENT,
Unique_Users INT NOT NULL,
PRIMARY KEY (Id)
);
Stored Procedure
CREATE PROCEDURE History.Status_Reporting(reporting_date DATE)
BEGIN
INSERT INTO history.status_report (Unique_Users) VALUES (10);
SELECT *
FROM History.Status_Report WHERE Id = LAST_INSERT_ID();
END;
Entity
package com.test;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedNativeQuery;
import javax.persistence.Table;
import java.io.Serializable;
#NamedNativeQuery(name = "callStatusReporting", query = "CALL Status_Reporting(:reporting_date)", resultClass = StatusReportEntity.class)
#Entity
#Table(name = "Status_Report", catalog = "History")
public class StatusReportEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "Id")
protected Integer id;
#Column(name = "Unique_Users")
protected int uniqueUsers;
public Integer getId() {
return this.id;
}
public int getUniqueUsers() {
return this.uniqueUsers;
}
}
Class under test
package com.test;
import org.hibernate.Query;
import org.hibernate.Session;
public class MyListener {
public StatusReportEntity doRequest(Date reportDate) {
Session session = HibernateUtil.openSession(); // returns a MySQL session or H2 if testing…
try {
Query query = session.getNamedQuery("callStatusReporting").setParameter("reporting_date", reportDate);;
StatusReportEntity statusReportEntity = (StatusReportEntity) query.uniqueResult();
return statusReportEntity;
} catch (Exception e) {
System.err.println(e);
} finally {
session.close();
return null;
}
}
H2 Aliases
To enable testing using H2, There is also a file to specify the necessary aliases:
CREATE SCHEMA IF NOT EXISTS History;
CREATE ALIAS IF NOT EXISTS Status_Reporting FOR "com.test.StoredProcs.statusReporting";
Test Class to be used by Alias
And a test class to return a default result from the SP call:
package com.test;
import com.test.StatusReportEntity;
public class StoredProcs {
public static StatusReportEntity statusReporting(Date reportingDate) {
StatusReportEntity statusReportEntity = StatusReportEntity.builder().withId(1).withUniqueUsers(10).build();
return statusReportEntity;
}
}
Test Class
package com.test;
import com.test.MyListener;
import java.util.Calendar;
import org.junit.Test;
import static org.junit.Assert.*;
public class MyListenerTest {
private MyListener listener;
#Test
public void listenerReturnsLatestData() throws Exception {
MyListener myListener = new MyListener();
assertNotNull(myListener.statusReporting(Calendar.getInstance().getTime()));
}
}
CREATE TABLE PERSON(
PERSON_ID IDENTITY PRIMARY KEY,
GIVEN_NAME VARCHAR(20),
FIRST_NAME VARCHAR(20),MIDDLE_NAME VARCHAR(20),
LAST_NAME VARCHAR(20),TITLE VARCHAR(20),NAME_SUFFIX VARCHAR(20));
The entity class must use any Generation strategy.
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name ="PERSON_ID")
private int personId;