JPA OneToMany with same entity using JoinTable - jpa

I have a OneToMany relationship that I need to model in JPA. I have a class called CustomerItem that needs to have certain CustomerItems (list) related to it. Therefore, I modeled it using JoinTable to keep this in a separate table.
I've tried to model it like this:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "mp_related_item", joinColumns = {
#JoinColumn(name = "customer_item_id", referencedColumnName = "id")})
private Set<CustomerItem> relatedItems;
I've created my table using Liquibase:
<createTable tableName="MP_RELATED_ITEM">
<column autoIncrement="true" name="ID" type="BIGINT">
<constraints primaryKey="true"
primaryKeyName="PK_MP_RELATED_ITEMS" />
</column>
<column name="CUSTOMER_ITEM_ID" type="BIGINT" />
<column name="RELATED_ITEM_ID" type="BIGINT" />
</createTable>
However, this results in an exception when trying to load relatedItems for a specific CustomerItem:
com.microsoft.sqlserver.jdbc.SQLServerException: Ogiltigt kolumnnamn, 'relatedItems_ID'. (Illegal column name)
What am I doing wrong here? When I check the generated query it seems like the persistence framework thinks that relatedItems is one single entity, when in reality it's a list of multiple entities that I want to load.

Related

JPA one-to-one bidirectional mapping on same object using association table

I have a table Person and now I want to express a relation like "best friend". Assuming a person can only have one best friend I don't want to alter the Person table to add a best friend column, rather I want to have an additional mapping table, e.g.:
Table Person (id name):
1 foo
2 bar
3 somebody
4 somebodyelse
Table BestFriendMapping (personId bestfriendId):
1 2
3 4
I was doing something like this:
class Person {
#OneToOne()
#Fetch(FetchMode.SELECT)
#JoinTable(name = "BestFriendMapping",
joinColumns = #JoinColumn(name = "personId", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "bestfriendId",
referencedColumnName = "id"))
private Person bestFriend;
}
The problem is, that now when I add a new Person, the mapping table is populated with two entries, for example the newly added Person is having id=10 and his bestFriend 20, then the entries are:
10 20
20 10
I would like to have just one entry, but still be able to get the best friend of a person no matter which I have in my hand currently. I found out that I probably have done two unidirectional instead of one bi-directional mapping, so I have to use mappedBy, but I am not sure what is the syntax when it is about the one and the same entity object, thus one and the same field inside the object. The examples on the internet are always showing the mapping of two entities via a mapping table.
Or maybe something like this?!? In addition to the JoinColumns and InverseJoinColumns to add mappedBy to the OnetoOne just like this #OneToOne(mappedBy="bestFriend"), kind of weird :)

How set foreign key to null when entity keys are of type bigint (ulong)?

In my model I'm using bigint (ulong) as the type for entity keys. I want the database to enforce referential integrity, so I have set persistenceEnforce to true. Columns for foreign keys are nullable. With referential integrity an entity can only be deleted if no foreign key is referring to the entity, so before deleting an entity I must first set each foreign key for this associated entity to null. However, I don't know how to clear the foreign key.
Here is my model:
<cf:entity name="Order" cfom:bindingList="false">
<cf:property name="Id" typeName="ulong" key="true" persistenceIdentity="true" cfps:hint="CLUSTERED" />
<cf:property name="Invoice" typeName="Invoice" persistenceEnforce="true" />
</cf:entity>
<cf:entity name="Invoice" cfom:bindingList="false">
<cf:property name="Id" typeName="ulong" key="true" persistenceIdentity="true" cfps:hint="CLUSTERED" />
<cf:property name="Name" typeName="string" />
</cf:entity>
Here is my code:
Invoice invoice = new Invoice();
invoice.Save();
Order order = new Order();
order.Invoice = invoice;
order.Save();
// We must clear the reference to the invoice before deleting the invoice,
// because the database enforces referential integrity.
order.InvoiceId = 0;
order.Save();
invoice.Delete();
The above code throws the following exception when saving the order for the second time:
The UPDATE statement conflicted with the FOREIGN KEY constraint \"FK_Ord_Ore_Inv_Inv\".
This is because the code produced by CodeFluent inserts the value 0 instead of null into the "Order_Invoice_Id" column. The following line in the Order.BaseSave method seems to be wrong:
persistence.AddParameter("#Order_Invoice_Id", this.InvoiceId, ((ulong)(0ul)));
I tried the settings persistenceDefaultValue="null" and usePersistenceDefaultValue="true" on the Invoice propery, but that did not solve the problem.
Note: A property of type UInt64 (unsigned) is translated to a column of type bigint (signed). So be careful with the conversions... FYI CodeFluent Entities uses CodeFluent.Runtime.Utilities.ConvertUtilities to convert values.
The overload AddParameter(string name, ulong value, ulong defaultValue) doesn't use the default value, so it does not translate the default value to NULL. As a workaround, you can create a PersistenceHook that changes the value of the parameter to match the expected behavior:
public class CustomPersistenceHook : BasePersistenceHook
{
public override void AfterAddParameter(string name, object value, IDbDataParameter parameter)
{
if (value is ulong && name == "#Order_Invoice_Id")
{
var defaultValue = (ulong)ContextData["defaultValue"];
if (defaultValue == (ulong)value)
{
parameter.Value = DBNull.Value;
}
}
base.AfterAddParameter(name, value, parameter);
}
}
Then, you need to register the persistence hook:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="Sample" type="CodeFluent.Runtime.CodeFluentConfigurationSectionHandler, CodeFluent.Runtime" />
</configSections>
<Sample persistenceHookTypeName="Sample.CustomPersistenceHook, Sample" />
</configuration>

Fetching multiple embedded objects mybatis

Class User{
private String employeeId;
private Country assignedCountry;
private Region employeeRegion;
//getter & setter
}
Class Country{
private String countryCode;
private Region countryRegion;
//getter & setter methods
}
Class Region{
private String regionCode;
}
<resultMap type="User" id="userResultMap">
<id column="employee_id" property="employeeId" />
<association property="assignedCountry" resultMap="countryResultMap"/>
<association property="employeeRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Country" id="countryResultMap">
<id column="country_cd" property="countryCode" />
<association property="countryRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Region" id="regionResultMap">
<id column="region_cd" property="regionCode" />
<id column="region_nm" property="regionName" />
</resultMap>
Employee is assigned to a country and also belongs to a region.
Country belongs to a region, which may or may not be the same as the employee's region.
The query will fetch the users assigned country and region.
select U.*, C.*, R.* from
User U left outer join Country C
on U.assigned_country_cd = C.country_cd
left outer join Region R
on U.employee_region_cd = R.region_cd
When I execute the query via mybatis and check the user object, I can see that the user region is set properly.
But I can also see that the Region object within the User's country is also set to the user's region. Which shouldn't be the case.
(I understand that I am not fetching the country region here. But if so, this object should not be set at all, rather than setting employee region to the country region)
Can some one please help me on how to map the Country's region within the country object?
I am very new to mybatis and ORM. Any help to shed some light into this would be appreciated.
In your query, you are joining the Region table, not with Country table, but with User table, which ultimately returns the region of the employee (and the countryRegion property of the Country object as well).
In your ResultMap, regionResultMap maps as follows:
region_cd -> regionCode
region_nm -> regionName
You are mapping region_cd column, both to the user.assignedCountry.countryRegion.regionCode and to the user.employeeRegion.regionCode in the same time that basically sets the same columns to same properties in different objects.
What you can do is to differentiate User's region and Country's region in your SQL and map in MyBatis accordingly:
Add another join to connect region with the country:
select U.*, C.*, R.*, CR.region_cd AS C_region_cd, CR.region_nm as C_region_nm from
User U left outer join Country C
on U.assigned_country_cd = C.country_cd
left outer join Region R
on U.employee_region_cd = R.region_cd
left outer join Region CR
on CR.belongsTo = C.country_cd
And, you need to change your ResultMap as follows in order to use the same ResultMap with a different column name. Here, belongsTo is the column in your Region table that shows which Country it belongs to :
<resultMap type="User" id="userResultMap">
<id column="employee_id" property="employeeId" />
<association property="assignedCountry" resultMap="countryResultMap"/>
<association property="employeeRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Country" id="countryResultMap">
<id column="country_cd" property="countryCode" />
<association columnPrefix="C_" property="countryRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Region" id="regionResultMap">
<id column="region_cd" property="regionCode" />
<id column="region_nm" property="regionName" />
</resultMap>

How to make the myBatis select result(list) be set to object's property?

Generally, myBatis's select method returns single object or generic List types. for example, I want to fetch all students of a class:
<select id="fetchStudentsOfClass" parameterType="int" resultMap="resultMapStudent">
SELECT * FROM students WHERE class_id=#{id}
</select>
I can easily get a result like this: List<Student>.
Now, if I want to get the result like this rather than List<Student>:
class MyClass
{
List<Student> getStudents{return this.students;}
void setStudents(List<Student> students){this.students = students}
private List<Student> students;
}
how can I do?
This is what the <collection/> element is for. It's important that you properly mark both the container and the values with their <id/> so that MyBatis knows how to deal with collapsing multiple rows into one object.
<resultMap id="resultMapClass" type="some.package.MyClass" autoMapping="true">
<id property="classId" column="class_id" javaType="integer"/>
<collection property="students" ofType="some.package.Student" autoMapping="true">
<id property="studentId" column="student_id" javaType="integer"/>
</collection>
</resultMap>
<select id="fetchStudentsOfClass" parameterType="int" resultMap="resultMapClass">
SELECT *
FROM students
WHERE class_id = #{id}
</select>
See http://mybatis.github.io/mybatis-3/sqlmap-xml.html#Result_Maps for more details.

jpa OneToMany & ManyToOne

i have a oneToMany and ManyToOne mapping in my models:
class User
#OneToMany (cascade = { CascadeType.REFRESH, CascadeType.DETACH }, fetch = FetchType.EAGER)
private Set<Judgement> judgements;
class Judgement
#ManyToOne(cascade = { CascadeType.REFRESH, CascadeType.DETACH})
#JoinColumn(name = "user_id")
private User judge;
and in DB, i have to tables as Users and Judgements, when i tried to run my code, it showed error as:
Caused by: org.postgresql.util.PSQLException: ERROR: relation "users_judgements" does not exist
does that mean i have to create the table users_judgements by hand, jpa cannot automatically create the relationship for me? RoR can do it...Thanks.
If you have a foreign key from USER table, user_id, in JUDGMENT table then there is no need to have another table. That will be the #JoinColumn(name = "user_id").
See if you are missing something; I can not see any "mappedBy" attribute, which will be "user" in your case.
Please take a look at this article; page 4 that the link will take you, give details about one-to-many relationship. It will be worth reading the whole article.
http://www.javaworld.com/javaworld/jw-01-2008/jw-01-jpa2.html?page=4
Also, the part 1 of the same tutorial is good for basics;
http://www.javaworld.com/javaworld/jw-01-2008/jw-01-jpa1.html