I have a device and device_group table, mapping by a device_group_mapping table as below
CREATE TABLE device_group_mapping
(
device_id character varying(64) NOT NULL,
device_group_id bigint NOT NULL,
CONSTRAINT "FK_device_group_mapping_device" FOREIGN KEY (device_id)
REFERENCES device (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT "FK_device_group_mapping_device_group" FOREIGN KEY (device_group_id)
REFERENCES device_group (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
The device and deviceGroup entity of openjpa as below
#Entity
#Table(name = "device")
public class Device implements Serializable
{
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "device_group_mapping", joinColumns =
{#JoinColumn(name = "device_id", referencedColumnName = "id", nullable = false)}, inverseJoinColumns =
{#JoinColumn(name = "device_group_id", referencedColumnName = "id", nullable = false)})
private List<DeviceGroup> deviceGroupCollection;
}
#Entity
#Table(name = "device_group")
public class DeviceGroup implements Serializable
{
#ManyToMany(mappedBy = "deviceGroupCollection", fetch = FetchType.EAGER)
#OrderBy()
private List<Device> deviceCollection;
}
Due to the fetch type is lazy, I have to get the deviceGroupCollection as below code
#Override
#Transactional
public List<Device> findAllDevicesWithGroupMapping() throws Exception
{
List<Device> list = new ArrayList<Device>();
list = this.deviceDao.findAll();
for (Device device : list)
{
device.setDeviceGroupCollection(device.getDeviceGroupCollection());
}
return list;
}
However, this will be very slow when list of devices contains amount of devices.
I think maybe I could just find device entity by JPQL with fetch join the device_group, but don't know how to do it. According to openjpa spec., it doesn't support on clause and also nested fetch join.
The openjpa I currently used as below
<dependency>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-all</artifactId>
<version>2.2.2</version>
</dependency>
Any help is appreciated.
You use a fetch join an a ManyToMany like on any other association. You don't need any on clase, since the association mapping already defines how the two entities are linked to each other:
select d from Device d
left join fetch d.deviceGroupCollection
where ...
Related
I would like to know why spring jpa isn't deleting the rows that are causing a constraint error or a way to make it possible to edit my Account entity with new roles.
The following entities are involved in the action i'm trying to perform.
#Entity
#Table(name = "account")
class AccountEntity(uuid: UUID? = null,
#Column(nullable = false) val email: String,
#Column(nullable = false) val password: String,
#OneToMany(
mappedBy = "accountUuid",
cascade = [CascadeType.ALL],
fetch = FetchType.LAZY
) val accountRolesEntity: List<AccountRolesEntity>) : BaseEntity(uuid)
#Entity
#Table(name = "account_roles")
class AccountRolesEntity(uuid: UUID? = null,
#Column(nullable = false) val accountUuid: UUID,
#OneToOne val role: RoleEntity) : BaseEntity(uuid)
#Entity
#Table(name = "role")
class RoleEntity(uuid: UUID? = null,
#Column(nullable = false) val name: String ) : BaseEntity(uuid)
So i'm trying to update the roles of a specific account.
For example:
If X has roles 'viewer' and 'editor' and suppose i want to change it to viewer only.
I do the following steps:
Request account entity from database
set new accountRolesEntity (received from controller) to account
Call the jpa repository save method
Method in Service class:
fun updateExistingAccount(account: AccountDTO, adjustedRoles: List<RoleDTO>): AccountDTO {
val mappedRoles: List<AccountRolesEntity> = adjustedRoles.map { accountRolesMapper.map(account.uuid, it) }
val accountEntity = accountMapper.map(account, mappedRoles)
return accountMapper.map(accountRepository.save(accountEntity))
}
The error i'm getting is: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "account_roles_account_uuid_role_uuid_key"
This is because i have a constrain in my database to make sure that an account may not have duplicate roles. The create table statement is as following:
CREATE TABLE account_roles (
uuid UUID PRIMARY KEY,
account_uuid UUID NOT NULL REFERENCES account(uuid),
role_uuid UUID NOT NULL REFERENCES role(uuid),
UNIQUE (account_uuid, role_uuid)
);
There is a fix for this by performing all the actions 1 by 1: Delete first and then make new inserts. But there should be a better way for this.
actually "AccountRolesEntity" is not an entity, it's a table which keeps the relationships of two entity by keeping ids of that entities into the table.
so for the first step, you should have something like this,
#JoinTable(name = "account_role",
joinColumns = #JoinColumn(name = "account")
, inverseJoinColumns = #JoinColumn(name = "role"))
#OneToMany(mappedBy = "accountUuid",
cascade = [CascadeType.ALL],
fetch = FetchType.LAZY)
val accountRolesEntity: List<AccountRolesEntity>) :BaseEntity(uuid)
and I think your problem depends on your cascades and your class diagram so check them again.
I want to delete Recipe (using spring data DAO) but I got SQL exception: org.postgresql.util.PSQLException: ERROR: update or delete on table "recipe" violates foreign key constraint "fkacys689tmdmfggtf4thdoc83k" on table "favourite_recipes"
Detail: Key (id)=(76823) is still referenced from table "favourite_recipes".
My entities:
#Entity
public class Account {
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "favourite_recipes",
joinColumns = #JoinColumn(name = "account_id"),
inverseJoinColumns = #JoinColumn(name = "recipe_id"))
private Set<Recipe> favouriteRecipes = new HashSet<>(0);
...
}
#Entity
public class Recipe {
...
}
How to remove recipe instance?
You need to handle the cascade type, by default is set to ALL.
For example you can work around the contraints like this:
#ManyToMany(cascade = CascadeType.DETACH)
more info : cascade type docs
in you need to delete from the owning entity side which is the Account.
So first remove the recipe from recipe list in Account and save the account, then remove the recipe itself.
As Amer Qarabsa metioned I had to remove recipe from Account.
I added new field in Recipe to get bidirectional mapping
#ManyToMany(cascade = CascadeType.MERGE, mappedBy = "favouriteRecipes")
private Set<Account> recipeLovers = new HashSet<>(0);
Code in service class to remove recipe from all accounts + clear lovers in recipe (recipe and recipeId variables are not initialized here)
Set<Account> recipeLovers = recipe.getRecipeLovers();
recipeLovers.forEach(account ->
account.getFavouriteRecipes()
.removeIf(r -> r.getId() == recipeId));
recipeLovers.clear();
recipeDao.delete(recipe);
I have this Parent class
#Entity
#Table(name = "category")
#NamedQuery(name = "category.findAll", query = "SELECT c FROM Category c")
public class Category implements Serializable {
public Category(){}
#Column(name = "name", nullable = false)
#Id
private String name;
#Column(name = "col2")
private Boolean col2;
}
And i have referenced the parent table in child table as follows:
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "cat_name")
private Category category
when i run this JPQL query
update Category c SET c.name=:newName ,c.termsCanHaveChildren=:canHaveChdrn where c.name=:oldName
it's return with foreign key constraint error while i have put Cascade All in child field
Cannot delete or update a parent row: a foreign key constraint fails (`terms`.`term`, CONSTRAINT `FKaykenypxci167nqioh4xx9p3a` FOREIGN KEY (`cat_name`) REFERENCES `category` (`name`))
The problem lays at the constraint being generated by your persistence provider (hibernate), for the #JoinColumn(name = "cat_name") at the child table (and not with the CascadeType that you're defining)...
The generated constraint should indicated that when the PK of Category is Updated, any reference to such column should be updated also...
I believe this configuration should work (but you need to test it first, because I always generated my database model using scripts and not using hibernate features):
#ManyToOne
#JoinColumn(
name = "cat_name",
foreignKey = #ForeingKey(
name = "fk_child_category",
foreignKeyDefinition = "FOREIGN KEY (cat_name) REFERENCES category ON UPDATE CASCADE"
)
)
private Category category;
Also you need to check if your database supports "ON UPDATE CASCADE"... According to this link, oracle does not... (What database are you using?)
If this does not work, try the suggestion of Michelle...
That's expected: you are changing the Primary Key (#Id), that's used in a Foreign Key (#JoinColumn).
Use a surrogated immutable primary key.
I have a situation that is quite similar to the one outlined in this question's diagram: JPA. JoinTable and two JoinColumns, although with different issues.
I have three tables: Function, Group, and Location. Currently, I have a join table set up between Location and Group using #JoinTable. It is #ManyToMany on both sides, and works perfectly fine.
I am attempting to add the constraint that no Location should be associated with more than one Group that has the same Function. So I added a column for Function to my join table in my SQL schema and a uniqueness constraint across the Location and Function columns, like so:
create table function_table (
id varchar(50),
primary key(id)
);
create table group_table (
id varchar(50),
function_id varchar(50) not null,
primary key(id)
);
alter table group_table add constraint FK_TO_FUNCTION foreign key (function_id) references function_table;
create table location_table (
id varchar(50),
primary key(id)
);
create table group_location_join (
location_id varchar(50) not null,
group_id varchar(50) not null,
function_id varchar(50) not null,
primary key(location_id, group_id, function_id),
unique(location_id, function_id)
);
alter table group_location_join add constraint FK_TO_LOCATION foreign key (location_id) references location_table;
alter table group_location_join add constraint FK_TO_GROUP foreign key (group_id) references group_table;
alter table group_location_join add constraint FK_TO_FUNCTION foreign key (function_id) references function_table;
I then attempted to set up the following in my model entities:
#Entity
#Table(name = "function_table")
public class Function {
#Id
#Column(name = "id", length = 50)
private String id;
}
#Entity
#Table(name = "group_table")
public class Group {
#Id
#Column(name = "id", length = 50)
private String id;
#ManyToOne
#JoinColumn(name = "function_id", referencedColumnName = "id", nullable = false)
private Function function;
#ManyToMany
#JoinTable(name = "group_location_join",
joinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id"),
#JoinColumn(name = "function_id", referencedColumnName = "function_id")},
inverseJoinColumns = #JoinColumn(name="location_id", referencedColumnName = "id"))
private Set<Location> locations;
}
#Entity
#Table(name = "location_table")
public class Location {
#Id
#Column(name = "id", length = 50)
private String id;
#ManyToMany
#JoinTable(name = "group_location_join",
joinColumns = #JoinColumn(name="location_id", referencedColumnName = "id")
inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id"),
#JoinColumn(name = "function_id", referencedColumnName = "function_id")})
private Set<Group> groups;
}
(Obviously, there is more to these entities, but I stripped them down to only the parts relevant to this question.)
This does not work. When I write a simple test to create a Location associated with a Group that is associated with a Function, the minute I try to flush the session to commit the transaction, Hibernate gives me this:
java.lang.ClassCastException: my.package.Group cannot be cast to java.io.Serializable
I think what's happening is that Hibernate is getting confused, throwing up its hands, and saying "I'll just serialize it, send it to the database, and hope it knows what's going on."
When I add implements Serializable and add a serialVersionUID to Group, I then get this:
org.hibernate.exception.SQLGrammarException: user lacks privilege or object not found: FUNCTION_ID
I'm not really sure how to proceed at this point, or if perhaps I have already proceeded too far down the wrong path. Maybe I'm not thinking about the SQL correctly, and there is a much easier way to ensure this constraint that doesn't involve all this ridiculousness.
Edit: In my system, the DAOs for the tables involved have no save capabilities. Which means that as long as my constraint is set up in the database, my application doesn't care; it can't insert things that violate the constraint because it can't insert things at all.
Edit 2: I never originally solved the stated problem, and instead simply added a third column in my database schema without touching the Java code, as stated in my first Edit section above. But I have since experimented with creating an explicit join table object with an #Embedded compound key, and it seems to work.
You are trying to create a composite primary key. In Hibernate you can do it using the #Embeddable annotation. In the example below you can find the way to use a composite key for two entities.
I believe you can move forward with this example and create your own version of primary key.
Mapping ManyToMany with composite Primary key and Annotation:
We have the following two entities with many-to-many association:
#Entity
public class Role {
...
#ManyToMany
#JoinTable( name = "user_has_role", joinColumns = { #JoinColumn( name = "role_fk" ) }, inverseJoinColumns = { #JoinColumn( name = "user_fk" ) } )
private Set<User> userCollection;
...
}
and
#Entity
public class User {
...
//bi-directional many-to-many association to Role
#ManyToMany( mappedBy = "userCollection" )
private Set<Role> roleCollection;
...
}
If we want to truncate all data with
em.createQuery( "DELETE Role" ).executeUpdate();
we have to clear all associations in the "user_has_role" JoinTable like shown in this answer:
for ( ... )
{
A a = aDao.getObject(aId);
B b = bDao.getObject(bId);
b.getAs().remove(a);
a.getBs().remove(b);
bDao.saveObject(b);
}
Is there a way to do delete all associations in the JoinTable at once without iterating over all the data?
Maybe there is a special HQL-Command like DELETE Role.user_has_role ?
While the JPA spec clearly writes that bulk operations are not cascaded to related entities (section 4.10 Bulk Update and Delete Operations), I expect providers to deal at least with join tables. Sadly, Hibernate doesn't and this is logged in HHH-1917. Workaround: use native SQL.