public class Primary{
private long id;
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(
name = "id"
)
public void getId(){return id;}
//other vars
}
public class Secondary{
//other vars
private Primary primary;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "primary_id", unique = true)
public Primary getPrimary(){return primary;}
}
From this, it is pretty easy to get a Primary Object from a Secondary Object. However, how do I get Secondary Object from a Primary Object without selecting twice in Hibernate?
The Primary class should look like this:
#OneToMany(mappedBy="primary", cascade={CascadeType.ALL})
private List<Secondary> secondaries;
// getter and setter
And you should make a small modification in Secondary:
#JoinColumn(name = "id", unique = true)
public Primary getPrimary(){return primary;}
Cause join column should refer to the joined field(id) in Primary.
The answer could be quite different based on what you are looking for.
Based on your current mapping, assuming you have a Primary instance on hand, you can get its corresponding Secondary by querying. E.g. by HQL:
from Secondary s where s.primary = :primary
and pass in your primary as the parameter.
If you are looking for way to navigate from a Primary object instance, you could have created a bi-directional mapping:
class Primary {
#OneToMany(mappedBy="primary", ...)
private Set<Secondary> secondaries;
}
class Secondary {
#ManyToOne
private Primary primary;
}
You could refer to my (very old) answer on related question on how to define it. https://stackoverflow.com/a/13812047/395202
However, simply having a bi-directional relationship DOES NOT avoid "selecting twice", if your "selecting twice" means running 2 SQL queries in DB.
To reduce such round-trip, there are several way to solve. First one is to declare the relationship as EAGER fetch. However this is a way that I don't usually recommend so I won't go deeper.
Another (imho, more appropriate) way is to do a join fetch when you are fetching Primary. To fetch the Primary instance with its related Secondary instances, use a HQL like :
from Primary p left join fetch p.secondaries where ....
Add this Setter method in the Secondary Class
public Primary getPrimary() {
return primary;
}
And get the primary object from Secondary Class
Related
I am building a blog system, and like to provide the upvote/downvote feature for the blog. Since the vote count number of blog should be persisted, i choose to use MySQL to act as the data store. And i use Spring JPA(Hibernate) to do the ORM job. Here's my data objects:
class Blog{
// ...
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(optional = false, fetch = FetchType.EAGER)
#PrimaryKeyJoinColumn
private BlogVoteCounter voteCounter;
}
And the counter class:
#Entity
public class BlogVoteCounter extends ManuallyAssignIdEntitySuperClass<Long> {
#Id
private Long id;
private Integer value;
}
The reason why i separate the BlogVoteCounter from Blog is that i think the voteCount field will be modified by a totally different frequency comparing to other fields of Blog, since i want to use cache to cache the Blog, following this guide, i choose to separate them.
However, since the VoteCount field might be always needed when return the Blog object to the front end, and to avoid the n+1 problem, i declared the BlogVoteCounter field in Blog class with EAGER fetch type.
I've already seen this article. Thus according to my personal comprehension, i use unidirectional relationship and only declare OneToOne in the Blog side.
However, when i examine the query, it turns out that jpa will still trigger a secondary query to retrieve BlogVoteCounter from database without simply using a join when use findAll method on BlogRepository.
select
blogvoteco0_.id as id1_2_0_,
blogvoteco0_.value as value2_2_0_
from
blog_vote_counter blogvoteco0_
where
blogvoteco0_.id=?
So how should i config, to always make the BlogVoteCounter field in Blog be fetched eagerly.
The usage of ManuallyAssignIdEntitySuperClass is following the Spring JPA doc, since i manually assign id for BlogVoteCounter class.
#MappedSuperclass
public abstract class ManuallyAssignIdEntitySuperClass<ID> implements Persistable<ID> {
#Transient
private boolean isNew = true;
#Override
public boolean isNew() {
return isNew;
}
#PrePersist
#PostLoad
void markNotNew(){
this.isNew = false;
}
}
And the BlogRepository is derived from JpaRepository
public interface BlogRepository extends JpaRepository<Blog, Long>{
// ...
}
I trigger the query by using findAll method, but using findById or other conditional query seems no difference.
When to fetch vs How to fetch : fetchType defines when to fetch the association ( instantlyvs later when someone access) the association but not how to fetch the association(i.e second select vs join query). So from JPA Spec point of view, EAGER means dont wait until someone access that field to populate it but JPA provider is free to use JOIN or second select as long as they do it immediately.
Even though they are free to use join vs second select, still I thought they should have optimised for join in the case of EAGER. So interested in finding out the logical reasoning for not using the join
1. Query generated for repository.findById(blogId);
select
blog0_.id as id1_0_0_,
blog0_.vote_counter_id as vote_cou2_0_0_,
blogvoteco1_.id as id1_1_1_,
blogvoteco1_.value as value2_1_1_
from
blog blog0_
inner join
blog_vote_counter blogvoteco1_
on blog0_.vote_counter_id=blogvoteco1_.id
where
blog0_.id=?
2. Updated Mapping
public class Blog {
#Id
private Long id;
#ManyToOne(optional = false, cascade = ALL, fetch = FetchType.EAGER)
#PrimaryKeyJoinColumn
private BlogVoteCounter voteCounter;
public Blog() {
}
public Blog(Long id, BlogVoteCounter voteCounter) {
this.id = id;
this.voteCounter = voteCounter;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public BlogVoteCounter getVoteCounter() {
return voteCounter;
}
public void setVoteCounter(BlogVoteCounter voteCounter) {
this.voteCounter = voteCounter;
}
}
3. Issues with current Mapping
As per your mapping, it is impossible to create blog and votecounter as it causes a chicken and egg problem.
i.e
blog and votecounter need to share the same primary key
blog's primary key is generated by database.
so in order to get the primary key of blog and assign it to votecounter as well, you need to store blog first
but the #OneToOne relationship is not optional, so you cannot store blog first alone
4.Changes
Either need to make the relationship optional so blog can be stored first, get the id, assign to BlogVoteCounter and save the counter
Or Don't auto generate Id and manually assign the id so blog and votecounter can be saved at the same time.(I have gone for this option but you can do first option)
5.Notes
default repository.findAll was generating 2 queries so I overridden that method to generate one join query
public interface BlogRepository extends JpaRepository<Blog, Long> {
#Override
#Query("SELECT b from Blog b join fetch b.voteCounter ")
List<Blog> findAll();
}
select
blog0_.id as id1_0_0_,
blogvoteco1_.id as id1_1_1_,
blog0_.vote_counter_id as vote_cou2_0_0_,
blogvoteco1_.value as value2_1_1_
from
blog blog0_
inner join
blog_vote_counter blogvoteco1_
on blog0_.vote_counter_id=blogvoteco1_.id
I'm new to hibernate and quite new to MySQL too.
I have the following two tables:
CREATE TABLE storeman.user (
id INT NOT NULL AUTO_INCREMENT,
email VARCHAR(80) NOT NULL,
display_name VARCHAR(50),
password CHAR(41),
active BOOLEAN NOT NULL DEFAULT FALSE,
provisional BOOLEAN NOT NULL DEFAULT FALSE,
last_login TIMESTAMP,
PRIMARY KEY (id),
UNIQUE INDEX (email)
);
CREATE TABLE storeman.user_preferences (
id INT NOT NULL AUTO_INCREMENT,
notify_login BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (id),
CONSTRAINT id_foreign FOREIGN KEY (id) REFERENCES user (id) ON DELETE CASCADE
);
In Eclipse, with hibernate tools I have generated the domain code classes. User.java looks like this (siplified):
#Entity
#Table(name = "user", catalog = "storeman", uniqueConstraints = #UniqueConstraint(columnNames = "email"))
public class User implements java.io.Serializable {
private Integer id;
[...]
private UserPreferences userPreferences;
public User() {
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
[...]
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
public UserPreferences getUserPreferences() {
return this.userPreferences;
}
}
My issue is with getUserPreferences: of course, that would return null if creating a new user or reading from the db where the corresponding row in the user_detail table does not exist. This is correct, however it forces me to check if userPreferences is not null before accessing its members. And from a coding point of view it is not so handy. So I changed User.getUserPreferences method like this, to get a default value:
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
public UserPreferences getUserPreferences() {
if (this.userPreferences==null)
this.userPreferences = new UserPreferences();
return this.userPreferences;
}
This is working fine, however if I ever would need to re-generate domain code (User.java) with hibernate tools, that change will be lost. So my question is: is there a way (even by modifying mySQL table/relationships) to automatically have userPreferences always set?
There is no way to do this outside of your code (at least not that I can think of), with some configuration or something like that.
One thing you can do is to initialize the relation when you declare it
private UserPreferences userPreferences = new UserPreferences()
but that also won't survive code regeneration. The only other way I can think of is to put this initialization code into some util method so you can maintain it there regardless of regeneration of entity code.
UserUtils.getUserPreferences(User user)
However, this would only work for the code you write, if some framework needs it you will again get null values because it will not use your util method (the first approach is better in this case).
Do bear in mind that, when you initialize this object on a managed entity, the new object will be persisted into the database.
User user = userDAO.getUser(id);
user.getUserPreferences(); // this code initializes the relation (new UserPreference())
After these lines, you will get a row in user_preferences table if cascade is configured in that manner, or you will get an exception complaining about transient entity found in entity you are trying to persist.
All that being said, is it really that hard to check if something is null, especially if by business rules it is allowed to be null?
I am creating JPA entities to store information about customers. I have an abstract class called customer. It has two child classes called shoppers and users. Both shoppers and users have metadata about them in the form of key value pairs which I have created another class called MetaData to store. My question is, how do I add an ordered column to metadata via the abstract class of customers? Here is some code so you can see what I am saying:
#Inheritance( strategy = TABLE_PER_CLASS )
public abstract class Customer implements Serializable
{
#OneToMany( mappedBy = "parent", orphanRemoval = false )
#OrderColumn //THIS IS CAUSING AN ERROR, BUT I WANT AN ORDERED COLUMN - PLEASE HELP
private List<MetaDataType> metaData;
}
The user and shopper class are the same essentially, nothing special here -
#Entity
public class User extends Customer implements Serializable
{
...some user specific stuff
}
Here is the metaData class-
#Entity
public class MetaData implements Serializable
{
#EmbeddedId
protected MetaDataId id;
#ManyToOne
#JoinColumn( name = "parentGuid", referencedColumnName = "guid", insertable = false, updatable = false )
protected Customer parent;
....
}
If I just have one child class say User, this works fine and the metaData table gets a column called metaData_order and all is well. The problem is when I add the Shopper entity, now the metaData table tries to insert two MetaData_order columns and throws this exception -
java.sql.SQLSyntaxErrorException: Column name 'METADATA_ORDER' appears more than once in the CREATE TABLE statement.
Call: CREATE TABLE METADATA (VALUE VARCHAR(255), parentGuid VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL, metaData_ORDER INTEGER, metaData_ORDER INTEGER, PRIMARY
KEY (parentGuid, name))
If I add a third child class that implements Customer, the statement tries to insert three metaData_ORDER columns. Obviously I am not doing this abstraction correctly, what am I missing?
If the target object is shared, then you need to use a #JoinTable to store the relationship and the OrderColumn.
If your case, you could probably reuse the same order column, as the same MetaData should never have more than one owner. This seems to be a bug in EclipseLink's TABLE_PER_CLASS support, try the latest release/build, and if still fails please log a bug and vote for it. You could also try a different type of inheritance, such as JOINED, TABLE_PER_CLASS is not normally a good solution. You could also create the table yourself with your own script.
I am not sure what I am missing to make a bidirectional onetomany relationship (hibernate engine). A scaled down version of the domain model:
class Person {
#OneToMany(mappedBy="personFrom", cascade = CascadeType.PERSIST)
public List<Relationship> relationships;
}
class Relationship {
#ManyToOne
public Person personFrom;
#ManyToOne
public Person personTo;
}
Some of the observations:
1. with the above mapping, there is no join table created.
2. When I remove the mappedBy (#OneToMany(cascade = CascadeType.PERSIST) ), the join table is created and i could persist Relationship through Person. "personFrom" field is empty, but I think that is normal as the relation is maintained through the join table.
I also tried by specifying join column at Relationship, didn't make any difference. Any help, highly appreciated.
thanks.
Edit:1
As per Dan's comment, if it matters to see the full content of the domain class, I have expanded them below.
class Relationship extends Model{
#ManyToOne
public RelationshipType relationshipType;
#ManyToOne
public Person personFrom;
#ManyToOne
public Person personTo;
#ManyToOne
public Person createdBy;
#ManyToOne
public Role roleFrom;
#ManyToOne
public Role roleTo;
#Override
public String toString() {
return relationshipType.toString();
}
}
class Person extends Model {
public Date dateCreated;
#Lob
public String description;
#OneToMany(cascade = CascadeType.ALL)
public List<Role> roles;
#OneToMany(mappedBy="personFrom", cascade = CascadeType.PERSIST)
public List<Relationship> relationships;
}
Role also related to Person, but I think keeping the personFrom, personTo helps to optimize my queries.
Role extends Model {
#ManyToOne
public RoleType roleType;
#ManyToOne
public Person createdBy;
}
with the above mapping, there is no join table created.
A join table is not required for a OneToMany, you'll get foreign key column in the Many side. And this is what I get when using your code:
create table Person (
id bigint not null,
primary key (id)
)
create table Relationship (
id bigint not null,
personFrom_id bigint,
personTo_id bigint,
primary key (id)
)
alter table Relationship
add constraint FK499B69164A731563
foreign key (personTo_id)
references Person
alter table Relationship
add constraint FK499B691698EA8314
foreign key (personFrom_id)
references Person
Which is the expected result (at least for me). Maybe what you actually want is a ManyToMany.
When I remove the mappedBy (#OneToMany(cascade = CascadeType.PERSIST) ), the join table is created and i could persist Relationship through Person. "personFrom" field is empty, but I think that is normal as the relation is maintained through the join table.
I wrote a small unit test using the provided code (with Hibernate's API but this doesn't change anything) and I don't get what the problem is (the session is created before the test method and the method runs inside a transaction):
Person p1 = new Person();
Person p2 = new Person();
Relationship r = new Relationship();
// create the personFrom bi-directional association
r.setPersonFrom(p1);
List<Relationship> relationships = new ArrayList<Relationship>();
relationships.add(r);
p1.setRelationships(relationships); // these four lines should be moved to some
// link management method (see update below).
// create the personTo uni-directional association
r.setPersonTo(p2);
session.persist(p2);
session.persist(p1);
assertNotNull(p2.getId());
assertNotNull(p1.getId());
assertNotNull(r.getId());
The above code results in two insert in the Person table and one insert in the Relationship table (valuing the 3 columns).
As I said, I don't get the problem. Maybe you should explain what the expected result is (both the relational model and the queries).
Update: To be totally clear, when working with bi-directional associations, you have to set both sides of the link and a common pattern is to use defensive link management methods to correctly set both sides of the association. Something like this:
public void addToRelationships(Relationship relationship) {
if (this.relationships == null) {
this.relationships = new ArrayList<Relationship>();
}
this.relationships.add(relationship);
relationship.setPersonFrom(this);
}
This is detailed in the section 1.2.6. Working bi-directional links of the Hibernate documentation.
Did you specify the foreign key column name as the name of your join column? Assuming the foreign key column is named PERSON_ID, the code should look something like this:
class Relationship {
#ManyToOne
#JoinColumn(name="PERSON_ID")
public Person personFrom;
...
}
I'm using Hibernate 3.5.2-FINAL with annotations to specify my persistence mappings. I'm struggling with modelling a relationship between an Application and a set of Platforms. Each application is available for a set of platforms.
From all the reading and searching I've done, I think I need to have the platform enum class be persisted as an Entity, and to have a join table to represent the many-to-many relationship. I want the relationship to be unidirectional at the object level, that is, I want to be able to get the list of platforms for a given application, but I don't need to find out the list of applications for a given platform.
Here are my simplified model classes:
#Entity
#Table(name = "TBL_PLATFORM")
public enum Platform {
Windows,
Mac,
Linux,
Other;
#Id
#GeneratedValue
#Column(name = "ID")
private Long id = null;
#Column(name = "NAME")
private String name;
private DevicePlatform() {
this.name = toString();
}
// Setters and getters for id and name...
}
#Entity
#Table(name = "TBL_APP")
public class Application extends AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "NAME")
protected String _name;
#ManyToMany(cascade = javax.persistence.CascadeType.ALL)
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
#JoinTable(name = "TBL_APP_PLATFORM",
joinColumns = #JoinColumn(name = "APP_ID"),
inverseJoinColumns = #JoinColumn(name = "PLATFORM_ID"))
#ElementCollection(targetClass=Platform.class)
protected Set<Platform> _platforms;
// Setters and getters...
}
When I run the Hibernate hbm2ddl tool, I see the following (I'm using MySQL):
create table TBL_APP_PLATFORM (
APP_ID bigint not null,
PLATFORM_ID bigint not null,
primary key (APP_ID, PLATFORM_ID)
);
The appropriate foreign keys are also created from this table to the application table and platform table. So far so good.
One problem I'm running into is when I try to persist an application object:
Application newApp = new Application();
newApp.setName("The Test Application");
Set<DevicePlatform> platforms = EnumSet.of(Platform.Windows, Platform.Linux);
newApp.setPlatforms(platforms);
applicationDao.addApplication(newApp);
What I would like to happen is for the appropriate rows in the Platform table to created, i.e. create a row for Windows and Linux, if they don't already exist. Then, a row for the new application should be created, and then the mapping between the new application and the two platforms in the join table.
One issue I'm running into is getting the following runtime exception:
2010-06-30 13:18:09,382 6613126-0 ERROR FlushingEventListener Could not synchronize database state with session org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.model.Platform
Somehow, the platform set is not being persisted when I try to persist the application. The cascade annotations are supposed to take care of that, but I don't know what's wrong.
So my questions are:
Is there a better way to model what I want to do, e.g. is using an Enum appropriate?
If my model is alright, how do I properly persist all of the objects?
I've been struggling with this for hours, and I've tried to recreate all of the code above, but it might not be complete and/or accurate. I'm hoping someone will point out something obvious!
You should decide whether your Platform is an entity or not.
If it's an entity, it can't be an enum, because list of possible platforms is stored in the database, not in the application. It should be a regular class with #Entity annotation and you will have a normal many-to-many relation.
If it isn't an entity, then you don't need TBL_PLATFORM table, and you don't have a many-to-many relation. In this case you can represent a set of Platforms either as an integer field with bit flags, or as a simple one-to-many relation. JPA 2.0 makes the latter case simple with #ElementCollection:
#ElementCollection(targetClass = Platform.class)
#CollectionTable(name = "TBL_APP_PLATFORM",
joinColumns = #JoinColumn(name = "APP_ID"))
#Column(name = "PLATFORM_ID")
protected Set<Platform> _platforms;
-
create table TBL_APP_PLATFORM (
APP_ID bigint not null,
PLATFORM_ID bigint not null, -- the ordinal number of enum value
primary key (APP_ID, PLATFORM_ID)
);
and enum Platform without annotations.
Simple use below mapping on your entity. Suppose that we have:
public enum TestEnum { A, B }
Then in your Entity class:
#ElementCollection(targetClass = TestEnum.class)
#CollectionTable(
name = "yourJoinTable",
joinColumns = #JoinColumn(name = "YourEntityId")
)
#Column(name = "EnumId")
private final Set<TestEnum> enumSet= new HashSet<>();
The following example shows what the situation is when Module is an entity and Langue is an enum.
#Entity
public class Module {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String libelle;
#ElementCollection(targetClass = Langue.class, fetch = FetchType.EAGER)
#CollectionTable(name = "link_module_langue",
joinColumns = #JoinColumn(name = "module_id", referencedColumnName = "id"))
#Enumerated(EnumType.STRING)
#Column(name = "langue")
private Set<Langue> langues;
}
public enum Langue {
FRANCAIS, ANGLAIS, ESPAGNOLE
}
You should create link_module_langue table, please see the following sql code :
CREATE TABLE `link_module_langue` (
`module_id` BIGINT(20) NOT NULL,
`langue` VARCHAR(45) NOT NULL,
PRIMARY KEY (`module_id`, `langue`),
CONSTRAINT `module_fk`
FOREIGN KEY (`module_id`)
REFERENCES `module` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE);
NB: Langue is not an entity and would not have its own table.