I'm trying JPA with a very simple class for the Play! framework and I'm having some problems with the id column.
My sql database has only two columns:
CREATE TABLE IF NOT EXISTS `auto` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
)
And my model is:
#Entity
#Table(name = "auto")
public class Auto extends Model{
#Column(insertable = false, updatable = false)
public int id;
public String name;
public Auto(String name){
this.name = name;
}
}
Everything works fine without this part:
#Column(insertable = false, updatable = false)
public int id;
As soon as I add public int id; I'd get this error though: A JPA error occurred (Unable to build EntityManagerFactory): Repeated column in mapping for entity: models.Auto column: id (should be mapped with insert="false" update="false")
And that's the reason I've added the column annotation, but it doesn't work with that neither, now I'm getting:
A javax.persistence.PersistenceException has been caught, org.hibernate.PropertyAccessException: could not set a field value by reflection setter of models.Auto.id
I'm testing the model this way: new Auto("bmw").save(); save() is a method from the model class in the playframework.
Anyone know why I'm having this problem? Thanks!
Hmm, try it completely without the id field. Looks like Playframework auto-creates an Id field if extending the Model class. See here:
"...
If you have used JPA before, you know that every JPA entity must provide an #Id property. Here the Model superclass provides an automatically generated numeric ID, and in most cases this is good enough.
..."
class Model already adds an id field, of type Long. This is conflicting with the id field you add on your class definition.
Just remove the id field from Auto and it should work. I'm not sure if the definition of int(11) in your database is correct, but JPA should automatically solve that if required.
Should't the column be annotated similar to following ?
#Id
#GeneratedValue(generator="???_seq",strategy=GenerationType.SEQUENCE)
The problem you are having relates to underestimating what you are getting for ´free´ in the play framework.
Both the ID (your original question) and the getters and setters (your follow up comment) are generated automatically for you.
The id field comes from the Model class, which you are extending, and the getters and setters are automatically generated and used when you make a public field in your model, and then refer to it later as model.field.
While all models will have an Id provided for them, it is recommended to use your own custom IDs if they are to do anything more complex or meaningful.
If you need to add your own id field (for example, because it needs to be an Integer rather than a Long), you can extend GenericModel rather than Model.
Related
consider an entity with just an id and text field:
#lombok.Data
class Entity {
#javax.persistence.Id
UUID id;
String name;
}
consider that the table definition is as follows:
create table entity (
id uniqueidentifier not null primary key default newid(),
name varchar(max)
);
I am then curious why this doesn't work and how i could make it work:
UUID savedId = entityRepository.save(new Entity().setName("entity name")).getId();
In JPA, entity IDs can be either assigned by the application code, or generated (by the JPA provider, such as Hibernate, or by the database). In many situations, it's desirable to have the entity IDs be generated instead of applicaiton-assigned; it seems like that's what you are expecting.
If so, you need to annotate the id field with #GeneratedValue. For example:
class Entity {
#Id
#GeneratedValue
UUID id;
String name;
}
Note that there are considerations to be made regarding the generation strategy, so you'll want to educate yourself about them and make the right choice based on your situation. This is a good page that discusses the options. This SO Answer also is worth reading (the author is a well-known expert on JPA and Hibernate).
Could be possible change the name of the Entity variable ID in JPA? Currently, the DAO save method use the Entity ID.
We have multiple existing tables in our database and then, we will have to add the ID column to all tables. Is there any way I can avoid adding the fields?
I suppose one possibility will be to override the DAO methods, but any other ideas?
Thanks in advance.
So your entity is defined as:
#Entity
public class SomeEntity {
#Id
private Integer id;
//...
}
You don't have to name the variable id unless you really want to. You could name this someEntityId and JPA nor Hibernate cares. The only influence the property name will have is how the NamingStrategy implementation will map that to a column name in the database.
We can override the naming strategy and apply specific names to columns if we need where cases exist that the naming strategy doesn't work for us.
#Id
#Column(name = "some_entity_id")
private Integer id;
This essentially will map the id variable to a column named some_entity_id.
Hope that helps.
I have the following error when I'm trying to map an entity:
ORA-00942: table or view does not exist
I figured out that the problem is that hibernate is trying to find the name of the table in "non-capital" (lowercase) letters, but Oracle has the tables names in capital letters (although the tables were created with non-capital letters).
I can fix the problem if I add the annotations #Table and #Column putting the names in capital letters, but I don't want to add those annotations.
I would like to know if there is any different way to do it.
Sql:
create table foo(
id integer not null
);
alter table foo
add constraint foo_pk
primary key (id);
Entity that is not working:
#Entity
public class Foo {
#Id
private Long id;
//getter and setter
}
Entity that is working:
#Entity
#Table(name = "FOO")
public class Foo {
#Id
private Long id;
//getter and setter
}
Thanks!
You can define a custom NamingStrategy, to make the specific table and column names translation from your entity to Database. Here is some example how to do it.
So, all you need is to create an implementation of NamingStrategy interface or extend some of existing strategies, modifying it's behaviour with the behaviour you want to get and then register this new strategy via hibernate XML configuration parameter hibernate.ejb.naming_strategy or via Configuration class.
In a Spring MVC application using Hibernate and MySQL, I have an abstract superclass BaseEntity that manages the values of the IDs for all the other entities in the model. The id field uses #GeneratedValue. I am encountering a problem whenever my code tries to save any of the subclasses that extend BaseEntity. The problem comes with the choice of GenerationType for the #GeneratedValue.
At every place in my code where a subclass of BaseEntity tries to save to the underlying MySQL database, I get the following error:
ERROR SqlExceptionHelper - Table 'docbd.hibernate_sequences' doesn't exist
I have read many postings about this on SO and on google, but they either deal with other databases (not MySQL) or they do not deal with abstract superclasses. I cannot solve the problem by using GenerationType.IDENTITY because I am using an abstract superclass to manage id fields for all entities in the model. Similarly, I cannot use GenerationType.SEQUENCE because MySQL does not support sequences.
So how do I solve this problem?
Here is the code for BaseEntity.java:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
protected Integer id;
public void setId(Integer id) {this.id = id;}
public Integer getId() {return id;}
public boolean isNew() {return (this.id == null);}
}
Here is an example of the code for one of the entities that extends BaseEntity:
#Entity
#Table(name = "ccd")
public class CCD extends BaseEntity{
//other stuff
}
Here is the DDL:
CREATE TABLE IF NOT EXISTS ccd(
id int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
#other stuff
)engine=InnoDB;SHOW WARNINGS;
Here is the JPQL code in the DAO:
#Override
#Transactional
public void saveCCD(CCD ccd) {
if (ccd.getId() == null) {
System.out.println("[[[[[[[[[[[[ about to persist CCD ]]]]]]]]]]]]]]]]]]]]");
this.em.persist(ccd);
this.em.flush();
}
else {
System.out.println("]]]]]]]]]]]]]]]]]] about to merge CCD [[[[[[[[[[[[[[[[[[[[[");
this.em.merge(ccd);
this.em.flush();
}
}
EDIT:
The reason I cannot use #MappedSuperClass in this situation is that I need to have ManyToOne relationships that allow for multiple subtypes to be used interchangeably. Look at the AccessLog class below as an example. It has an actor_entity and a target_entity. There can be many types of actor entities and many types of target entities, but they all inherit from BaseEntity. This inheritance enables the underlying accesslogs data table in MySQL to just have one actor_entity_id field and just one target_entity_id field instead of having to have several fields for each. When I change #Entity above BaseEntity to #MappedSuperClass, a different error gets thrown indicating that AccessLog cannot find BaseEntity. BaseEntity needs #Entity annotation in order for AccessLog to have polymorphic properties.
#Entity
#Table(name = "accesslogs")
public class AccessLog extends BaseEntity{
#ManyToOne
#JoinColumn(name = "actorentity_id")
private BaseEntity actor_entity;
#ManyToOne
#JoinColumn(name = "targetentity_id")
private BaseEntity target_entity;
#Column(name="action_code")
private String action;
//getters, setters, & other stuff
}
SECOND EDIT:
As per JBNizet's suggestion, I created a hibernate_sequences table as follows:
CREATE TABLE IF NOT EXISTS hibernate_sequences(
sequence_next_hi_value int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY
)engine=InnoDB;SHOW WARNINGS;
But now I am getting the following error:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'sequence_name' in 'where clause'
Here is the hibernate sql causing the error, followed by the next 2 lines of the stack trace:
Hibernate: select sequence_next_hi_value from hibernate_sequences where sequence_name = 'BaseEntity' for update
ERROR MultipleHiLoPerTableGenerator - HHH000351: Could not read or init a hi value
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'sequence_name' in 'where clause'
How do I resolve this?
What a mess... AUTO_INCREMENT is MySQL's hidden sequence. The radical problem is that MySQL can not insert and return the PK at the same time, but Hibernate need this while INSERTing a new Entity.
The Problems you run into:
If Hibernate save a new Entity, he try to immerdentelly set the id to the new EntityBean. Therefore hibernate must read what ID will the Database use before hibernate save the new Tuple to the Table.
If you have multiple Servers who access the database, you shall let hibernate's session-factory decide to use the built-in sequence(AUTO-INCREMENT) or let hibernate decide (GenerationType.AUTO/GenerationType.IDENTITY) how large the open range of reserved PK's is (Job of a DB-Architect). (We have about 20 servers to one Database, so on a good-used table we use a PK-distance of +100). If only one server have access to the database GenerationType.TABLE shall be correct.
Hibernate must calculate the next id by yourself using max(*)+1 but:
What if two requests ask for max(*)+1 at the same time/with the same result? Right: The last try to insert will fail.
So you need to have a Table LAST_IDS in the database who stores the last Table-PK's. If you like to add one, you must do this steps:
Start read-optimistic transaction.
SELECT MAX(address_id) FROM LAST_IDS
store the maximum in a java-variable i.e.: $OldID.
$NewID = $OldID + 1. (+100 in pessimistic-lock)
UPDATE LAST_IDS SET address_id= $newID WHERE address_id= $oldID?
commit the read-optimistic transaction.
if commit was successfull, store $newID to setID() in the HibernateBean you like to save.
Finally let Hibernate call the insert.
This is the only way i know.
BTW: Hibernate-Entitys shall only use inheritance if the Database support inheritance between tables like PostgreSQL or Oracle.
Because you use the TABLE identifier generator you need to have that table created. If you are not using the enhanced identifier generators, chances are you are going to use the MultipleHiLoPerTableGenerator.
The MultipleHiLoPerTableGenerator can use one table for all table identifier generators.
My suggestion is to grab the table ddl from your integration tests, in case you use hbmddl to build the test schema. If you use flyway or liquibase for testing, you can add a maven plugin to generate the ddl schema.
Once you have the schema, you need to take the exact create table command and make add it to your MySQL database.
I am trying to establish a relationship between 2 entities which would be zero-to-one. That is, the Parent can be saved without the associated Child entity and also along with the assoicated Child.
Following are the 2 Entity classes...
Employee (Parent)
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name="EMP_NAME")
private String name;
#PrimaryKeyJoinColumn
#OneToOne(cascade = {CascadeType.ALL})
private EmployeeInfo info;
#Column(name="EMP_ENUM")
private Integer enumId;
EmployeeInfo (Child)
public class EmployeeInfo {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="EMPLOYEE_EMAIL")
private String email;
With such kind of a relation and id column of the only Parent (Employee) table set to AUTO INCREMENT in MySql DB, the problem is that while saving a Parent->Child object graph, I get the following exception
org.springframework.orm.hibernate3.HibernateJdbcException: JDBC exception on Hibernate data access: SQLException for SQL [insert into EMP_INFO
Caused by: java.sql.SQLException: Field 'id' doesn't have a default value
I tried setting the Child Table's Id property to AUTO INCREMENT in the DB , and the persistence of such a Parent->Child object graph is successful.
However, the problem described here surfaces, because I have a scenario in which I would like to save the parent (Employee) object without the associated EmpInfo object, and hence do NOT want to have AUTO INCREMENT on the Child's id column.
One solution could be not use the PrimaryKeyJoinColumn, but use a particular JoinColumn, but that adds an unnecessary column to my existing Table.
Has anyone come across such a problem? If yes, any pointers would be much helpful.
Finally, I got it working thanks to Pascal and some googling from my side. Apparently, I cannot use the Native key generator for such relationships where the parent can exist without the child (optional = true).
The thing that worked finally was the following, leaving me the downside of having to deal with Hibernate specific annotation (#GenericGenerator) and also having to make-do with bi-directional relationships instead of the unidirectional that I wanted.
Employee (Parent) class remains unchanged as above. It has AUTO INCREMENT on the Id column.
As for the child class (EmployeeInfo) it changed to the following, and again WITHOUT having the AUTO INCREMENT set on the Id column.
#Table(name="EMP_INFO")
#Entity
public class EmployeeInfo {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="verifInfo")})
private Long id;
#OneToOne(optional=false)
#JoinColumn (name="id")
private Employee emp;
#Column(name="EMPLOYEE_EMAIL")
private String email;
This helped me achieve what I wanted but on the downside, GenericGenerator is not a JPA annotation, it is a hibernate annotation, and sadly I have to make do with that as of now because JPA does not currently support this(or any similar) annotation.
Anyway, it helps to get through such cases :-)
I have a scenario in which I would like to save the parent (Employee) object without the associated EmpInfo object.
The optional attribute of a OneToOne is true by default, which is what you want.
However, you are somehow misusing the #PrimaryKeyJoinColumn here (well, it actually depends on what you really want to achieve but your current combination of annotations is not correct).
IF you want to map a OneToOne with a shared primary-key, use the #PrimaryKeyJoinColumn. But in that case, don't use a GeneratedValue on EmployeeInfo and set the id manually or, if you don't want to set it manually, use the Hibernate specific foreign generator that I already mentioned in your previous question. Check also the related question mentioned below.
And IF you do not want to use a shared primary key (like in your current code since you're trying to get the id generated by the database), then do not use the PrimaryKeyJoinColumn.
You have to make a choice.
References
JPA 1.0 specification:
9.1.32 PrimaryKeyJoinColumn Annotation
Related question
JPA Hibernate One-to-One relationship.