Hibernate one class to many tables - java

I'm wondering if there's a possibility to fetch multiple tables to only one java class
for exemple :
TABLE LABELS;
TABLE STANDARDS;
TABLE REFERENCES;
mapped to the same class
public Class Information {
private String type; // the type is the element who have to do the mapping => LABELS/STANDARDS/REFERENCES
...
}
It's not possible for me to construct one class for each type for technical reason (I known that some heritage should be cool).
Thank you
Gilles
EDIT :
I'll try to expain a bit more :)
I'm using a JMS service to get the informations. Each message have a particulary type, (in my exemples : "labels","standards" and "references").
By using those type, I want to persit the informations in the respective Tables. The structure is exactly the same for every messages, it's why I wanna use a unique POJO.
I hope it was better explain :)
EDIT 2 :
TABLE LABELS (
ID PRIMARY KEY AUTO_INCREMENT,
MESSAGE VARCHAR(255),
AUTHOR VARCHAR(255)
);
TABLE STANDARDS(
ID PRIMARY KEY AUTO_INCREMENT,
MESSAGE VARCHAR(255),
AUTHOR VARCHAR(255)
);
TABLE REFERENCES (
ID PRIMARY KEY AUTO_INCREMENT,
MESSAGE VARCHAR(255),
AUTHOR VARCHAR(255)
);
and here's some examples of JMS
headers :
type : label
body:
{message:"name of the country",author:"john doe"}
headers :
type : label
body:
{message:"nom du pays",author:"jenny doe"}
headers :
type : reference
body:
{message:"country",author:"john doe"}
and I want to put them into the Information Class and persist them into the correct Table

Try this:
#MappedSuperclass
public class Base {
private String message;
private String autor;
#Column(name = "MESSAGE")
public String getMessage(){
return message;
}
public void setMessage(final String message) {
this.message = message;
}
#Column(name = "AUTOR")
public String getAutor(){
return autor;
}
public void setAutor(final String autor) {
this.autor = autor;
}
}
And three classes:
#Entity
#Table(name="LABELS")
public class Labels extends Base{};
And
#Entity
#Table(name="STANDARDS")
public class Standards extends Base{};
And
#Entity
#Table(name="REFERENCES")
public class References extends Base{};
Now you can persist the data using:
Base b;
if (info.getType().equals("REFERENCES")) {
b=new References();
} else if (info.getType().equals("LABELS")) {
b=new Labels();
} else if (info.getType().equals("STANDARDS")) {
b=new Standards();
} else {
return;
}
b.setMessage(info.getMessage());
b.setAutor(info.getAutor());
Transaction t = session.beginTransaction();
session.persist(b);
t.commit();

You can use secondaryTables:
http://docs.oracle.com/javaee/6/api/javax/persistence/SecondaryTables.html

You might be asking if you can do a table per subclass mapping. You can have one class per table, but all inherit from a common base class.
Alternately, if you're building a read-only mapping, you could make a view that is a UNION over all the relevant tables, and just map the view using Hibernate. Something like
-- SQL
CREATE VIEW information AS
SELECT 'LABEL' AS type,... FROM labels
UNION
SELECT 'STANDARD',... FROM standards
UNION
SELECT 'REFERENCE',... FROM references
;
/* Java */
#Entity
#Table(name="information")
public class Information {
...
}

Related

Hibernate fetching deleted java enum types from db

I have an Enum class which has some values.
We've decided to remove one of these values and its all implementation from the code.
We dont want to delete any records from DB.
My Enum class is something like this:
public enum CourseType {
VIDEO("CourseType.VIDEO"),
PDF("CourseType.PDF"),
QUIZ("CourseType.QUIZ"),
SURVEY("CourseType.SURVEY"),
POWERPOINT("CourseType.POWERPOINT") //*this one will be removed*
...
}
My Course Entity:
#Entity
#Table(name = "CRS")
public class Course {
#Column(name = "COURSE_TYPE")
#Enumerated(EnumType.STRING)
private CourseType courseType;
#Column(name = "AUTHOR")
private String author;
....
#Override
public CourseType getCourseType() {
return courseType;
}
#Override
public void setCourseType(CourseType courseType) {
this.courseType = courseType;
}
....
}
After I removed the Powerpoint type from the Java Class and tried to fetch some values from the DB,
I get a mapping error for the removed type.
I have a code like this:
Course course = courseService.get(id);
If I gave a course id which its type is 'POWERPOINT' in the database,
the method gets the following error:
java.lang.IllegalArgumentException: Unknown name value [POWERPOINT]
for enum class [com.tst.enums.CourseType] at
org.hibernate.type.EnumType$NamedEnumValueMapper.fromName(EnumType.java:461)
at
org.hibernate.type.EnumType$NamedEnumValueMapper.getValue(EnumType.java:449)
at org.hibernate.type.EnumType.nullSafeGet(EnumType.java:107) at
org.hibernate.type.CustomType.nullSafeGet(CustomType.java:127) at
org.hibernate.type.AbstractType.hydrate(AbstractType.java:106) at
org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:2912)
at org.hibernate.loader.Loader.loadFromResultSet(Loader.java:1673)
Is there any way when I try to retrieve a query result from DB,
hibernate will not fetch if that records' course_type column doesn't match with the any of the enum values in the code?
Do I have to use some kind of filter?
You can try use annotation #filter
#Filter(name = "myFilter", condition = "courseType <> 'CourseType.POWERPOINT'")
and enable it
session.enableFilter("myFilter")
If you can't use filters,
something like the following should work:
Add POWERPOINT back into the enum.
Add a deleted flag to the POWERPOINT enum value.
After the course list is loaded, remove courses that have a deleted courseType value.
New CourseType enum:
public enum CourseType
{
VIDEO("CourseType.VIDEO", false),
POWERPOINT("CourseType.POWERPOINT", true);
private boolean deletedFlag;
public CourseType(
existingParameter, // do whatever you are currently doing with this parameter
deletedFlagValue)
{
// code to handle existing parameter
deletedFlag = deletedFlagValue;
}

Hibernate/JPA works fine in Eclipse, throws a SchemaManagementException when deployed

I'm using Hibernate with JPA to connect to a MySql database. The database is created before the application launch so Hibernate is not building the database for me. This application is a webapp that is to be deployed to tomcat.
For one table I am using some Generics to handle some values that may either be String, Integer or Boolean. The table is created with this statement:
CREATE TABLE IF NOT EXISTS `registry` (
`DTYPE` varchar(31) NOT NULL,
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`configName` varchar(255) NOT NULL,
`label` varchar(255) NOT NULL,
`configValue` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=latin1;
The classes it is handling are an abstract class base with three classes that extends the abstract. Like this:
#Entity
#Table(name = "registry")
public abstract class Config<T> implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true, nullable = false)
private String configName;
private String label;
// standard getters and setters for the above fields.
public abstract void setConfigValue(T value);
public abstract T getConfigValue();
}
#Entity
public class BooleanConfig extends Config<Boolean>{
private Boolean configValue;
public void setConfigValue(Boolean configValue){
this.configValue = configValue;
}
public Boolean getConfigValue(){
return this.configValue;
}
}
#Entity
public StringConfig extends Config<String>{
private String configValue;
public void setConfigValue(String configValue){
this.configValue = configValue;
}
public String getConfigValue(){
return this.configValue;
}
}
#Entity
public IntegerConfig extends Config<Integer>{
// and similar as the other two but with Integer.
}
All these classes are listed in my persistence.xml and when I run the application from Eclipse for debugging, everything works as expected and the values are written and edited as I would expect them to be. My problem is once I compile the war file and upload it to Tomcat. When the webapp is deployed to Tomcat there is an error during the start up of the application caused by the database.
SchemaManagementException: Schema-validation: wrong column type encountered in column [configValue] in table [registry]; found [varchar (Types#VARCHAR)], but expecting [bit (Types#BOOLEAN)]
Now I figure the answer to the problem is that I will need to go into each of the extending classes and map the configValue column to a specific data type. My question is why do I not receive the exception when running from my IDE?
Ok I apparently fixed this problem and totally forgot that I had posted this question. So here's what I was doing wrong. The problem basically comes from the handling of the configValue of the extending classes. Each one here used its own type but the database requires that the type is varchar(255). Once I provided a Column Definition for the configValue in the form of:
#Column(columnDefinition = 'varchar(255)')
private Integer configValue; // this could be for all the types listed in the question.
the problem solved itself. As for the reason it didn't show itself while debugging in Eclipse, I'm still not sure, but this fix caused no problems during debugging and solved the problem during deployment. I didn't figure this all out myself, but I can't remember who helped me.

RequestMapping Unable to populate Entity class where fields within EmbeddedId

I have the following Entity class:
#Entity
#Table(name="reporteddevicedata", schema="schemaName")
public class MobileDeviceData {
#EmbeddedId
MobileDeviceDataId mobileDeviceDataId;
#Column(name="activitydetecteddate")
private ZonedDateTime activityDetectedDate;
public void setFlagId(int flagId) {
mobileDeviceDataId.setFlagId(flagId);
}
......
}
#Embeddable
class MobileDeviceDataId implements Serializable {
#Column(name="clientid")
private int clientId;
#Column(name="flagid")
private int flagId;
}
My Controller code looks like this:
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
// code here
}
Originally I had my Entity class with just one primary key #ID on the clientId and it worked great. I would make a REST call and it would populate the MobileDeviceData class as expected. Then I switched to a composite ID using the #Embeddable and #EmbeddableId annotation and now the #RequestMapping is unable to populate the flagId parameter. Now when I make a REST call I get a null pointer exception for mobileDeviceDataId, thus its unable to update that field when it gets called and Throws a null pointer exception.
So my question is, how do I get an instance of the #Embeddable class? Can I just create one with new? I'm not sure of the implications of this since Spring may be expecting to make that value itself? What is the "normal" way this field would get updated via RequestMapping?
First of all you should avoid embedded id's, it just makes all things harder
surrogate primary keys are just easier to use, when you have a foreign key on a table with multi-column primary key it makes it much more complicated to deal with
now you faced these problems by yourself but according to your question
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
for(MobileDeviceData mobileDeviceData : deviceInfoList){
int clientId = mobileDeviceData.getMobileDeviceDataId().getClientId();
int flagId = mobileDeviceData.getMobileDeviceDataId().getFlagId();
MobileDeviceData foundMobileDeviceData = mobileDeviceDataService.findByClientIdAndFlagId(clientId, flagId);
if(foundMovileDeviceData == null){
mobileDeviceDataService.save(mobileDeviceData);
}else {
//update foundMobileDeviceData with mobileDeviceData fields
mobileDeviceDataService.save(foundMobileDeviceData);
}
}
}
else if you want to update just flag id
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
for(MobileDeviceData mobileDeviceData : deviceInfoList){
int clientId = mobileDeviceData.getMobileDeviceDataId().getClientId();
MobileDeviceData foundMobileDeviceData = mobileDeviceDataService.findByClientId(clientId);
if(foundMovileDeviceData == null){
mobileDeviceDataService.save(mobileDeviceData);
}else {
//update foundMobileDeviceData with mobileDeviceData
int flagId = foundMobileDeviceData.getMobileDeviceDataId().getFlagId();
MobileDeviceDataId mobileDeviceDataId = foundMobileDeviceData.getMobileDeviceDataId();
mobileDeviceDataId.setFlagId(mobileDeviceData)
mobileDeviceDataService.save(foundMobileDeviceData);
}
}
}
Next if you want to find something by client id just create a JPA Query like
"from MobileDeviceData WHERE mobileDeviceDataId.clientId = :clientId"
or native sql
"SELECT * FROM reporteddevicedata WHERE client_id = :someParam"
EXAMPLE JSON Request
[ {
"mobileDeviceDataId" : {
"clientId" : 0,
"flagId" : 0
},
"activityDetectedDate" : null
}, {
"mobileDeviceDataId" : {
"clientId" : 1,
"flagId" : 1
},
"activityDetectedDate" : null
}, {
"mobileDeviceDataId" : {
"clientId" : 2,
"flagId" : 2
},
"activityDetectedDate" : null
} ]
uglified ready to copy/paste version :
[{"mobileDeviceDataId":{"clientId":0,"flagId":3},"activityDetectedDate":null},{"mobileDeviceDataId":{"clientId":1,"flagId":1},"activityDetectedDate":null},{"mobileDeviceDataId":{"clientId":2,"flagId":2},"activityDetectedDate":null}]
Additionaly there should be some validation added on your MobileData object to avoid nullpointers when an invalid json request is send (with no mobileDeviceDataId present)
Finally answering the question:
Its not considered a good practice to use database model as container to share between API (because of primary keys, maybe some sensitive data, it depends).
Moreover if you want your embeddableId to work, request have to be build like in the example json. Proper fields have to be filled out. When requests arent build that way and they are just flat json without 'embedded id' you have to create some wrapper wich will fit the json format(this wrapper will be the requestbody class or List). Nextly you will have to convert wrapper to your db object with embedded id(created with a new keyword).
And this is why i suggest you not to use the composite or embedded id. This is simple example where do you have just one table, but when it comes to use foreign keys and multicolumn primary keys, the tabels are getting more complicated and messy, you make searching over db harder and this is why i suggest you to use surrogate id's without embedding anything, it makes things harder and is just messy

jpa non managed entities

Let's say I have to fire a query like this:
Select primarykey, columnname, old_value, new_value from first_audit_log;
Select primarykey, columnname, old_value, new_value from second_audit_log;
Select primarykey, columnname, old_value, new_value from third_audit_log; ...so on
audit_log is not mapped as JPA enity to any class and I strictly can't create n number of classes for n number of *_audit_logs.
Using native query feature, how best I can map this to a generic class? Trying to SELECT NEW feature, but not sure... Hence any help is appreciated.
Since your audit logs tables share the same columns, you can create a view that "unifies" those tables and map a single Java class to that view. I believe you can, since you don't need to write updates, I guess.
As an alternative, using native queries would be a good choice.
EDIT:
1) If your audit logs are already views, you can create a view based on other views, if you don't want to create a mapping Java class for each of them. Just remember to add a dummy column that has value 1 if the row comes from the "first" audit log, 2 if it comes from the second, and so on, so you can set them apart.
2) In order to use native queries, assuming your persistence provider is Hibernate, you can do like in this example:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
EntityManager em = emf.createEntityManager();
Session sess = em.unwrap(Session.class); // <-- Use Hibernate-specific features
SQLQuery query = sess.createSQLQuery(
"SELECT AVG(age) AS averageAge, AVG(salary) as averageSalary FROM persons");
query.setResultTransformer(Transformers.aliasToBean(MyResult.class));
MyResult result = (MyResult) query.list().get(0);
where MyResult is declared as follows:
public class MyResult {
private BigDecimal averageAge;
private BigDecimal averageSalary;
public BigDecimal getAverageAge() {
return averageAge;
}
public void setAverageAge(BigDecimal averageAge) {
this.averageAge = averageAge;
}
public BigDecimal getAverageSalary() {
return averageSalary;
}
public void setAverageSalary(BigDecimal averageSalary) {
this.averageSalary = averageSalary;
}
}
and the persons table is like this (MySQL syntax):
CREATE TABLE `persons` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`firstname` varchar(255) NOT NULL,
`lastname` varchar(255) NOT NULL,
`age` int(11) NOT NULL,
`salary` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
You can easily adapt this example to your needs, just replace persons and MyResult with what you want.
The aliases in the sql query is automatically converted to upper case and its looking for the setter in Upper case as a result org.hibernate.PropertyNotFoundException Exception is thrown. Any suggestions would be greatly appreciated.
For instance, the below statement is looking for the setter ID instead of Id/id (Could not find setter for ID on class Data)
List<Data> result = entityManager.unwrap(Session.class)
.createSQLQuery("Select id as id from table")
.setParameter("day", date.getDayOfMonth())
.setParameter("month", date.getMonthOfYear())
.setParameter("year", date.getYear())
.setResultTransformer(Transformers.aliasToBean(Data.class))
.list();
class Data {
Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}

Mapping Result From SP To Complex Object

I am trying to implement MyBatis in my project at work. It is a legacy system, which uses vanilla JDBC to access the database, solely through stored procedures. I understand that to call a stored procedure, MyBatis requires an object which contains the input parameters for the stored procedure and another that will hold the result set. Not sure if this is entirely true.
To prevent creating too many data entities in the system, I want to reuse the existing ones. And here is where the problem arises. Let me explain what the typical situation/scenario I am facing, and then how I am trying to solve it.
Let's say I have the following data entity(ies) in the system:
class Account {
private int accountID;
private String accountName;
private OrganizationAddress address;
// Getters-Setters Go Here
}
class OrganizationAddress extends Address {
// ... some attributes here
// Getters-Setters Go Here
}
class Address {
private String address;
private String city;
private String state;
private String country;
// Getters-Setters Go Here
}
I am using annotations, so my Mapper class has something like this:
#Select(value = "{call Get_AccountList(#{accountType, mode=IN, jdbcType=String})}")
#Options(statementType = StatementType.CALLABLE)
#Results(value = {
#org.apache.ibatis.annotations.Result
(property = "accountID", column = "Account_ID"),
#org.apache.ibatis.annotations.Result
(property = "accountName", column = "Organization_Name"),
#org.apache.ibatis.annotations.Result
(property = "state", column = "State", javaType=OrganizationAddress.class)
})
List<Account> getAccountList(Param param);
Problem: When I make the call to the stored procedure, the Account object has the state always null.
To add to the injury, I do not have access to the source of the above data entities. So I couldn't try the solution provided on this link either - Mybatis select with nested objects
My query:
Is it possible for me to use the data entites already present in the system, or do I have to create new ones, and then map the data to the existing ones?
If yes, how do I go about it? Any references, if any.
If no, is there a way to reduce the number of data entities I would create to call the stored procedures (for both in and out parameters)?
I think the best solution for your situation (if I understand it correctly) is to use a MyBatis TypeHandler that will map the state column to an OrganizationAddress object.
I've put together a example based on the information you provided and it works. Here is the revised annotated Mapper:
// Note: you have an error in the #Select line => maps to VARCHAR not "String"
#Select(value = "{call Get_AccountList(#{accountType, mode=IN, jdbcType=VARCHAR})}")
#Options(statementType = StatementType.CALLABLE)
#Results(value = {
#org.apache.ibatis.annotations.Result
(property = "accountID", column = "Account_ID"),
#org.apache.ibatis.annotations.Result
(property = "accountName", column = "Organization_Name"),
#org.apache.ibatis.annotations.Result
(property = "address", column = "State", typeHandler=OrgAddressTypeHandler.class)
})
List<Account> getAccountList(Param param);
You need to map the address field of Account to the "state" column and use a TypeHandler to create an OrganizationAddress with its "state" property filled in.
The OrgAddressTypeHandler I created looks like this:
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
public class OrgAddressTypeHandler extends BaseTypeHandler<OrganizationAddress> {
#Override
public OrganizationAddress getNullableResult(ResultSet rs, String colName) throws SQLException {
OrganizationAddress oa = new OrganizationAddress();
oa.setState(rs.getString(colName));
return oa;
}
#Override
public OrganizationAddress getNullableResult(ResultSet rs, int colNum) throws SQLException {
OrganizationAddress oa = new OrganizationAddress();
oa.setState(rs.getString(colNum));
return oa;
}
#Override
public OrganizationAddress getNullableResult(CallableStatement cs, int colNum) throws SQLException {
OrganizationAddress oa = new OrganizationAddress();
oa.setState(cs.getString(colNum));
return oa;
}
#Override
public void setNonNullParameter(PreparedStatement arg0, int arg1, OrganizationAddress arg2, JdbcType arg3) throws SQLException {
// not needed for this example
}
}
If you need a more complete working example than this, I'll be happy to send more of it. Or if I have misunderstood your example, let me know.
With this solution you can use your domain objects without modification. You just need the TypeHandler to do the mapping and you don't need an XML mapper file.
Also I did this with MyBatis-3.1.1 in MySQL. Here is the simple schema and stored proc I created to test it:
DROP TABLE IF EXISTS account;
DROP TABLE IF EXISTS organization_address;
CREATE TABLE account (
account_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
organization_name VARCHAR(45) NOT NULL,
account_type VARCHAR(10) NOT NULL,
organization_address_id SMALLINT UNSIGNED NOT NULL,
PRIMARY KEY (account_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE organization_address (
organization_address_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
address VARCHAR(45) NOT NULL,
city VARCHAR(45) NOT NULL,
state VARCHAR(45) NOT NULL,
country VARCHAR(45) NOT NULL,
PRIMARY KEY (organization_address_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO organization_address VALUES(1, '123 Foo St.', 'Foo City', 'Texas', 'USA');
INSERT INTO organization_address VALUES(2, '456 Bar St.', 'Bar City', 'Arizona', 'USA');
INSERT INTO organization_address VALUES(3, '789 Quux Ave.', 'Quux City', 'New Mexico', 'USA');
INSERT INTO account VALUES(1, 'Foo', 'Type1', 1);
INSERT INTO account VALUES(2, 'Bar', 'Type1', 2);
INSERT INTO account VALUES(3, 'Quux', 'Type2', 3);
DROP PROCEDURE IF EXISTS Get_AccountList;
DELIMITER $$
CREATE PROCEDURE Get_AccountList(IN p_account_type VARCHAR(10))
READS SQL DATA
BEGIN
SELECT a.account_id, a.organization_name, o.state
FROM account a
JOIN organization_address o ON a.organization_address_id = o.organization_address_id
WHERE account_type = p_account_type
ORDER BY a.account_id;
END $$
DELIMITER ;

Categories

Resources