So say I did something like this:
Employee emp=new Employee();
emp.setId(1); // PK
emp.setName("Earl");
emp.setAge("73");
session.save();
According to the tutorial here: http://www.mkyong.com/hibernate/hibernate-one-to-one-relationship-example/ , this should bring about an insert in the Employee table, assuming all mappings are correct. However, I keep on getting a serialization error. Is it because I am giving the ID a value? The query generates (However it takes Id as NULL... why is this?)
Is there any way I can verify barring checking the database to see if the query was done? Also, please do look at my other queries. I am very new to hibernate.
This can be done by keeping your id as generated value and you can type cast the return type to integer.In model class place this annotation upon id
#generatedvalue(strategy = generationtype.auto)
private int id;
Then there no need to insert the Employee id hibernate automatically generates the value and inserts and to check that values are inserted
Int i=(Integer)session.save(emp);
if(i > 0)
{
System.out.println("Values inserted");
}
First of all check that you pass a value to the function session.save():
session.save(emp);
If it still fails...
May be your primary key is set to auto increment. So you need not to specify a value for it.
Also try wrapping it in a transaction.
Just try this :
Transaction txn = session.beginTransaction();
Employee emp=new Employee();
//emp.setId(1); // PK
emp.setName("Earl");
emp.setAge("73");
session.save(emp);
txn.commit();
And best wishes..
Related
Is it possible to use GenerationType.IDENTITY with Transaction in Hibernate/Spring data?
I have an existing database, with an identity column in all tables. So, I have to use GenerationType.IDENTITY for it. But, when I create a new entity instance and change its state to managed with someRepository.save(...) method, the persistence provider can't acquire a new ID for that entity, because it must happen on flush time, at the end of the transaction.
If I create one entity, all works as expected. After save(), the entity goes to the managed state, the id is changed from NULL to 0 (zero), and at the flush time, the new ID for the row is generated by the database.
But what if we create two instances of the same entity class inside one transaction? The exception will be thrown, and this is justly because we have two different objects with same ID = 0. So, is there a way to deal with it without change strategy from IDENTITY to something else?
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
Long id;
...
}
#Transactional
public void brokenCode() {
Customer one = new Customer();
Customer two = new Customer();
someRepository.save(one);
someRepository.save(two); <--- org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session
}
CREATE TABLE [dbo].[Customer]
(
[id] [int] IDENTITY (1,1) NOT NULL,
...
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED ([id] ASC)
WITH (PAD_INDEX = OFF
, STATISTICS_NORECOMPUTE = OFF
, IGNORE_DUP_KEY = OFF
, ALLOW_ROW_LOCKS = ON
, ALLOW_PAGE_LOCKS = ON
, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [DATA]
)
UPD!
The reason is the table has "trigger instead of insert". In that trigger some fields are calculated based on other fields. And the identity doesn't return to Hibernate.
ALTER trigger [dbo].[Customer] on [dbo].[Customer]
instead of insert
as
if ##rowcount = 0 return
set nocount on
Set dateformat dmy
Insert into dbo.Customer
(f1, f2, ....)
Select
I.f1,
isnull(f2,newid()),
...
from Inserted I
So. Is there a way to somehow return identity to Hibernate from that trigger?
Thanks in advance!
I have 2 methods that exhibit a different behavior in regard to flushing in Hibernate.
The first one is:
#Transactional
public void firstMthod(int id, int status) {
Person entity = session.get(Person.class, id);
entity .setStatus(personStatus.registered);
session.merge(entity);
updatePersonAge(id,18);
}
The updatePersonAge method is located in another class, and the SQL output of this method looks like this:
select personel0_.ID as ID1_119_0_,
personel0_.status as status2_119_0_,
personel0_.age as age3_119_0_,
personel0_.CreatedBy as CreatedBy4_119_0_,
personel0_.UpdatedBy as UpdatedBy5_119_0_,
personel0_.CreatedDate as CreatedDate6_119_0_,
personel0_.UpdatedDate as UpdatedDate7_119_0_,
personel0_.Ip as Ip8_119_0_
from tbl_personel personel0_
where personel0_.ID = ?
update tbl_person set status = ? where ID = ?
update tbl_person set age = ? where ID = ?
and for the second use case, we have the following method:
#Override
#Transactional
public void secondMethod(int id,int courseId, int status) {
Course courseEntity=session.get(Course .class, courseId);
courseEntity.setCreatedDate(new Date());
session.merge(courseEntity);
updatePersonAge(id,18);
}
For which the updatePersonAge method generates the following SQL output:
select course0_.ID as ID1_120_0_,
course0_.CreatedBy as CreatedBy7_120_0_,
course0_.UpdatedBy as UpdatedBy8_120_0_,
course0_.CreatedDate as CreatedDate9_120_0_,
course0_.UpdatedDate as UpdatedDate10_120_0_,
course0_.Ip as Ip11_120_0_
from tbl_course course0_
where course0_.ID = ?
update tbl_course set created_date = ? where ID = ?
update tbl_person set age = ? where ID = ?
The updatePersoneAge method is :
public int updatePersonAge(int id,int age){
Query query = session.createQuery("update " + domainClass.getName() + " e set e.age= :age ");
query.setParameter("age ", age);
return query.executeUpdate();
}
According to my expectations, the output of the second method should be the same with the output of the first method. So why the difference? It is really confusing.
First of all, it makes no sense to call merge on an entity that is already attached to the currently running Session. Merge is meant to be used when you want to attach a detached entity.
Second, Hibernate FetchMode.AUTO flush only triggers the flush if the query about to be run overlaps with entities in the ActionQueue.
In the first example, because you modified the Person, and the query is run against a Person, it makes sense to trigger a flush as otherwise, the SQL query might return stale results.
In the second case, you modify a Course entity, yet you want to select from Person. So, there is no need to trigger the flush.
You can control this behavior using Query.addSyncronizedEntityName.
Apart from updating the age explicitly, inside the updatePersonAge method, in each of those transactions you are also updating implicitly (by getting an entity and changing one of the fields) other field of a managed entity.
As you are merging those changes, the PErsistence Provider has the obligation to flush those changes at the end of the transaction.
Thats why when you change the status:
Person entity = session.get(Person.class, id);
entity .setStatus(personStatus.registered);
session.merge(entity);
Hibernate persists that change along with the explicit age upate:
update tbl_person set status = ? where ID = ?
update tbl_person set age = ? where ID = ?
In the second method when you change a field of the Course entity:
Course courseEntity=session.get(Course .class, courseId);
courseEntity.setCreatedDate(new Date());
session.merge(courseEntity);
That change is persisted along with the explicit age update:
update tbl_course set created_date = ? where ID = ?
update tbl_person set age = ? where ID = ?
I'm currently using Spring and Hibernate framework, and have an entity with:
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="ID")
private Long id;
#Column(name="ACC_ID")
private Long accId;
Now, in a specific case I'd like to merge an object in the database using column "ACC_ID" instead of "ID", however, I do not want to assign #Id to accId because I do not want to change the entity itself.
Is there anything I can do on the merge function? (But apparently merge takes no other parameter than an object)
entityManager.merge(entityObject)
Thanks in advance for any clue or help. =)
entityManager.merge(entityObject) can be used if it is your primary key based.
If it is another unique constraint you'd have to handle it by yourself. First try to find an entity with that value (with a query).
If a match is found, copy the primary key to your new entity before saving as normal.
For example:
public Entity save(Entity entity, boolean rollback) {
// look for a match, you'll have to implement your own method here
Entity match = getEntityByValue("column_name", entity.getMergeColumn());
if (match != null) {
// copy the primary key
entity.setId(match.getId());
}
// save the entity
save(entity, rollback);
}
The application we have used MySQL until today and everything was fine. Now we need to use MSSQL.
A lot of our unit tests are now failing. A sample is as follows:
Caused by: java.sql.BatchUpdateException:
Violation of UNIQUE KEY constraint 'UQ__field_ty__5068257C6DE5E37D'.
Cannot insert duplicate key in object 'dbo.field_type_mapping'.
The duplicate key value is (<NULL>, -11).
As I said, this test is successful when using MySQL.
The table field_type_mapping has a constraint:
/****** Object: Index [UQ__field_ty__5068257C6DE5E37D]
ALTER TABLE [dbo].[field_type_mapping] ADD UNIQUE NONCLUSTERED
(
[mapping_entity_id] ASC,
[field_type_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
The test is as follows and the exception is thrown at the last line of this test:
Invoice document = documentDao.get(5000);
assertEquals("Document should have exactly one reference field!", 1, document.getFieldTypeMappings().size());
assertEquals("Document should have exactly one item!", 1, document.getDocumentItems().size());
Set<InvoiceItem> items = document.getDocumentItems();
InvoiceItem item = items.iterator().next();
assertEquals("Document's item should have no reference field!", 0, item.getFieldTypeMappings().size());
ReferenceFieldType referenceFieldType = referenceFieldTypeDao.get(-11L);
FieldTypeMapping documentFieldType = new FieldTypeMapping();
documentFieldType.setFieldType(referenceFieldType);
documentFieldType.setFieldValue("a value");
document.addFieldTypeMapping(documentFieldType);
FieldTypeMapping documentItemFieldType = new FieldTypeMapping();
documentItemFieldType.setFieldType(referenceFieldType);
documentItemFieldType.setFieldValue("another value");
item.addFieldTypeMapping(documentItemFieldType);
documentDao.save(document);
flush();
document = documentDao.get(id);
assertEquals("Reference object for document not added!", 2, document.getFieldTypeMappings().size());
items = document.getDocumentItems();
item = items.iterator().next();
assertEquals("Reference object for document item not added!", 1, item.getFieldTypeMappings().size());
document.addFieldTypeMapping(documentFieldType);
item.addFieldTypeMapping(documentItemFieldType);
documentDao.save(document);
flush();
document = documentDao.get(id);
assertEquals("Number of reference object should not have changed for document!", 2, document.getFieldTypeMappings().size());
items = document.getDocumentItems();
item = items.iterator().next();
assertEquals("Number of reference object should not have changed for document' item!", 1, item.getFieldTypeMappings().size());
document.getFieldTypeMappings().remove(documentFieldType);
item.getFieldTypeMappings().remove(documentItemFieldType);
documentDao.save(document);
flush(); // Exception is thrown at this point..
My understanding is something is wrong with:
item.getFieldTypeMappings().remove(documentItemFieldType);
as the exception is mentioning id -11 ?
The hibernate code for removal is as follows:
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
org.hibernate.annotations.CascadeType.DETACH,
org.hibernate.annotations.CascadeType.LOCK})
#JoinColumn(name = "mapping_entity_id")
#XmlTransient
#Fetch(FetchMode.SELECT)
public Set<FieldTypeMapping> getFieldTypeMappings() {
return fieldTypeMappings;
}
As I am pretty novice with this I do not even understand what might be wrong. How can I fix this issue? Is this an issue with hibernate and how it handles the queries? I also want to mention that all the db is created with hibernate as well, no manual sql execution and db creation is made.
You usually need the ability to have a null FK when you may not know the value at the time of entering the data, especially whilst you know other values to be entered.
To allow nulls in an FK generally all you have to do is allow nulls on the field that has the FK. The null value is separate from the idea of it being an FK. - This is what I believe you need to do.
Whether it is unique or not unique relates to whether the table has a one-one or a one-many relationship to the parent table.
I'm trying to follow the JPA tutorial and using ElementCollection to record employee phone numbers:
PHONE (table)
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Short version
What I need is a class like this:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#Embedded
List<Phone> phones;
}
that stores each person's phone numbers in a collection.
Long version
I follow the tutorial code:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#ElementCollection
#CollectionTable(
name="Phones",
joinColumns=#JoinColumn(name="owner_id")
)
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type")
String type = "";
#Column(name="number")
String number = "";
public Phone () {}
public Phone (String type, String number)
{ this.type = type; this.number = number; }
}
with a slight difference that I only keep one table. I tried to use the following code to add records to this table:
public static void main (String[] args) {
EntityManagerFactory entityFactory =
Persistence.createEntityManagerFactory("Tutorial");
EntityManager entityManager = entityFactory.createEntityManager();
// Create new entity
entityManager.getTransaction().begin();
Phone ph = new Phone("home", "001-010-0100");
PhoneId phid = new PhoneId();
phid.phones.add(ph);
entityManager.persist(phid);
entityManager.getTransaction().commit();
entityManager.close();
}
but it keeps throwing exceptions
Internal Exception: org.postgresql.util.PSQLException: ERROR: null
value in column "type" violates not-null constraint Detail: Failing
row contains (0, null, null). Error Code: 0 Call: INSERT INTO Phones
(owner_id) VALUES (?) bind => [1 parameter bound] Query:
InsertObjectQuery(tutorial.Phone1#162e295)
What did I do wrong?
Sadly, i think the slight difference that you only keep one table is the problem here.
Look at the declaration of the PhoneId class (which i would suggest is better called PhoneOwner or something like that):
#Entity
#Table(name="Phones")
public class PhoneId {
When you declare that a class is an entity mapped to a certain table, you are making a set of assertions, of which two are particularly important here. Firstly, that there is one row in the table for each instance of the entity, and vice versa. Secondly, that there is one column in the table for each scalar field of the entity, and vice versa. Both of these are at the heart of the idea of object-relational mapping.
However, in your schema, neither of these assertions hold. In the data you gave:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
There are two rows corresponding to the entity with owner_id 1, violating the first assertion. There are columns TYPE and NUMBER which are not mapped to fields in the entity, violating the second assertion.
(To be clear, there is nothing wrong with your declaration of the Phone class or the phones field - just the PhoneId entity)
As a result, when your JPA provider tries to insert an instance of PhoneId into the database, it runs into trouble. Because there are no mappings for the TYPE and NUMBER columns in PhoneId, when it generates the SQL for the insert, it does not include values for them. This is why you get the error you see - the provider writes INSERT INTO Phones (owner_id) VALUES (?), which PostgreSQL treats as INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null), which is rejected.
Even if you did manage to insert a row into this table, you would then run into trouble on retrieving an object from it. Say you asked for the instance of PhoneId with owner_id 1. The provider would write SQL amounting to select * from Phones where owner_id = 1, and it would expect that to find exactly one row, which it can map to an object. But it will find two rows!
The solution, i'm afraid, is to use two tables, one for PhoneId, and one for Phone. The table for PhoneId will be trivially simple, but it is necessary for the correct operation of the JPA machinery.
Assuming you rename PhoneId to PhoneOwner, the tables need to look like:
create table PhoneOwner (
owner_id integer primary key
)
create table Phone (
owner_id integer not null references PhoneOwner,
type varchar(255) not null,
number varchar(255) not null,
primary key (owner_id, number)
)
(I've made (owner_id, number) the primary key for Phone, on the assumption that one owner might have more than one number of a given type, but will never have one number recorded under two types. You might prefer (owner_id, type) if that better reflects your domain.)
The entities are then:
#Entity
#Table(name="PhoneOwner")
public class PhoneOwner {
#Id
#Column(name="owner_id")
long id;
#ElementCollection
#CollectionTable(name = "Phone", joinColumns = #JoinColumn(name = "owner_id"))
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type", nullable = false)
String type;
#Column(name="number", nullable = false)
String number;
}
Now, if you really don't want to introduce a table for the PhoneOwner, then you might be able to get out of it using a view. Like this:
create view PhoneOwner as select distinct owner_id from Phone;
As far as the JPA provider can tell, this is a table, and it will support the queries it needs to do to read data.
However, it won't support inserts. If you ever needed to add a phone for an owner who is not currently in the database, you would need to go round the back and insert a row directly into Phone. Not very nice.