I'm relatively new to JPA and Hibernate and am trying to see how the #OneTo One annotation works, let's say I have an entity "Task" with the following relation:
#OneToOne
#JoinColumn(name = "manager_id")
private Manager manager;
And there's the entity "Manager":
#Entity
#Table(name = "manager")
public class Manager {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
public Manager() {
}
When I run the test file along with the "hibernate.hbm2ddl.auto" set to "update" I get a Many to One relation in the database (as you can see, there is no unique constraint of any kind that'd make it a one to one relation):
CREATE TABLE IF NOT EXISTS `timesheet`.`task` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`completed` BIT(1) NOT NULL,
`description` VARCHAR(255) NULL DEFAULT NULL,
`manager_id` BIGINT(20) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `FK3635851B178516` (`manager_id` ASC),
CONSTRAINT `FK3635851B178516`
FOREIGN KEY (`manager_id`)
REFERENCES `timesheet`.`manager` (`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
To be sure of this I tried adding two records with the same manager id and were indeed added, I also tried setting the unique constraint like "#Table(name = "Task",uniqueConstraints = #UniqueConstraint(columnNames =..." but no luck.
So Why is this happening and what's exactly the pros of using #OneToOne annotaion if no application logic is applied to validate this?
Also, Is there any chance that Hibernate is not able to do the DDL generation properly?
(I know that generation of schemas through hibernate is only meant for testing)
In a unidirectional relationship you will get the expected unique constraint if you mark it as "optional=false". You also get it if you set the join column explicitly as unique, of course.
So either
#OneToOne(optional=false)
#JoinColumn(name = "manager_id")
private Manager manager;
or
#OneToOne
#JoinColumn(name = "manager_id", unique=true)
private Manager manager;
So why do you need to mark it as not optional?
My guess is that, when a value is optional, the column can contain many null values, but in many databases this can not be done when a unique constraint is present. You can do it in MySQL though, so maybe the Hibernate generator is not taking the database into account in this case (a bug?).
See a discussion about MySQL handling of nulls here.
I had this issue too and I just needed to add the referenced column so I can get a generated table:
#Entity(name = "news")
public class News extends BaseEntity {
#Column(length = 500)
private String title;
#Column(length = 2000)
private String description;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "file_id", referencedColumnName = "id", unique = true)
private Picture picture;
}
Related
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?
This is my entity class:
#Entity
#Table(name="APPEAL")
public class Appeal
{
#Id
#Column(name="ID_APPEAL")
#GeneratedValue(strategy = GenerationType.AUTO ,generator="SQ_APPEAL")
#SequenceGenerator(name="SQ_APPEAL", sequenceName="SQ_APPEAL")
private Long idAppeal;
#ManyToOne
#JoinColumn(name="ID_USER")
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#ManyToOne
#JoinColumn(name="ID_APPEAL_PARENT")
#OnDelete(action = OnDeleteAction.CASCADE)
private Appeal parent;
...
Now, to generate the SQL to create my tables I'm using this code:
for (#SuppressWarnings("rawtypes") final Class entity : classes) {
ejb3Configuration.addAnnotatedClass(entity);
}
dialectProps = new Properties();
dialectProps.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
hibernateConfiguration = ejb3Configuration.getHibernateConfiguration();
final StringBuilder script = new StringBuilder();
final String[] creationScript = hibernateConfiguration.generateSchemaCreationScript(Dialect
.getDialect(dialectProps));
But hibernate is not getting the cascade for the self reference:
create table APPEAL (ID_APPEAL bigint not null auto_increment, ID_USER bigint, ID_APPEAL_PARENT bigint, primary key (ID_APPEAL)) ENGINE=InnoDB;
alter table APPEAL add index FK_kcwnikcyoq8pskhdhnmtc0h9f (ID_USER), add constraint FK_kcwnikcyoq8pskhdhnmtc0h9f foreign key (ID_USER) references USER (ID_USER) on delete cascade;
alter table APPEAL add index FK_5ay1y0vn1nyeb9vgkpdb98q18 (ID_APPEAL_PARENT), add constraint FK_5ay1y0vn1nyeb9vgkpdb98q18 foreign key (ID_APPEAL_PARENT) references APPEAL (ID_APPEAL);
How should I define the cascade for the self referencing column?
Thanks
MySQL does not support it.
Deviation from SQL standards: If ON UPDATE CASCADE or ON UPDATE SET NULL recurses to update the same table it has previously updated during the cascade, it acts like RESTRICT. This means that you cannot use self-referential ON UPDATE CASCADE or ON UPDATE SET NULL operations.
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:
I have this scenario :
tbl_master(
master_field_pk int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`master_field_pk`)
)
and
tbl_detail(
`detail_field_pk` int(11) NOT NULL AUTO_INCREMENT,
`master_field_pk` int(11) DEFAULT NULL,
CONSTRAINT `child_fk1` FOREIGN KEY (`master_field_pk`) REFERENCES `tbl_master` (`master_field_pk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
PRIMARY KEY (`master_field_pk`)
)
This is my hibernate class for master :
#Entity
#Table(name = "tbl_master")
#Name("TblMaster")
public class TblMaster implements Serializable{
#Id
#Column(name = "master_field_pk")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer MasterFieldPk;
#Cascade({CascadeType.ALL,CascadeType.DELETE_ORPHAN})
#OneToMany(mappedBy="tblMaster")
#JoinColumn(name="master_field_pk", insertable=true, updatable=true, referencedColumnName="master_field_pk")
private Set<TblDetail> tblDetails;
#Transient
private List<TblDetail> tblDetailsList;
// any other things
}
and this one is for detail :
#Entity
#Table(name="tbl_detail")
#Name("TblDetail")
public class TblDetail implements Serializable{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="detail_field_pk")
private Integer detailFieldPk;
#Column(name="master_field_pk")
private Integer MasterFieldPk;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="master_field_pk", insertable = false, updatable = false, referencedColumnName = "master_field_pk")
private TblMaster tblMaster;
// any other things
}
To insert, i use this block of code (more or less) :
TblDetail detail_1 = ...
detail_1.setTblMaster(tblMaster);
// and so on ..
Set<TblDetails> set = ...
set.add(detail_1);
// and so on...
tblMaster.setTblDetails(set);
em.persist(tblMaster);
em.flush();
All the records (the master record and the details record) are perfectly inserted. However, the master_field_pk in tbl_detail - the foreign key - is still null. Is it possible to avoid this behavior? Thanks
No. You have to persist the master first. The detail rows need to know what the master ID is to be linked.
This can be done but only when the key is assigned by the application, for example GUID keys assigned by the application, or some sort of key server.
CascadeType.ALL should do the trick for you. It has worked for me all the time.
I think your problem lies in your #GeneratedValue attribute strategy. You should be using GenerationType.IDENTITY instead of GenerationType.AUTO.
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.