I am trying to use optimistic locking using the version field and no exception is being thrown when I call the save from the jpa repository. I am new to Spring and hibernate and I am worried that I am setting it up incorrectly.
The libraries i am using are:
hibernate4-maven-plugin version 1.0.2
hibernate-jpa02.0 1.0.1
spring-data-jpa version 1.3.4
So my entity is set up like this:
#Entity
public class MyEntity
{
#Id
protected Long id;
#Version
protected Long version;
protected String name;
public Long getVersion()
{
return version;
}
public void setVersion(Long version)
{
this.version = version;
}
public Long getVersion()
{
return version;
}
public void setVersion(Long version)
{
this.version = version;
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(Long id)
{
this.name = name;
}
}
I pass the version through to the client through my dto and pass it back when i do a save in my MyEntityStoreDao:
#Repository
public class MyEntityStoreDao extends BaseDao<MyEntityStoreDao>
{
private RepositoryManager myRepoManager;
#Autowired
public void setMyRepo(MyEntityRepository myRepo)
{
this.myRepo = myRepo;
}
public MyEntity save(MyEntityDTO dtoToUpdate)
{
Session session = this.Session();
MyEntity myEntity = new MyEntity();
if(dtoToUpdate.getId() > 0) {
myEntity = (MyEntity) session.get(MyEntity.class, dtoToUpdate.getId())
}
myEntity.setName(dtoToUpdate.getName());
MyEntity result = this.myRepo.save(myEntity);
this.repositoryManager.flush(myRepo);
}
}
The repositoryManager is in the BaseDao and is using the org.springframework.data.jpa.repository.JpaRepository.
The version is being updated correctly and incrementing. But when i do an update, I expect when the version being passed through from the DTO to save in the MyEntityStoreDao to not match what is in the database, it would throw a StaleStateException or OptmisticLockingException.
I checked and the versions do not match but the save still occurs. Any help on why this is happening? Thanks
Turn On sql logging by show-sql=true and see if the update query has the required where clause
where version = ?
If such where clause is missing then you need to add annotation #org.hibernate.annotations.Entity(dynamicUpdate = true)
With your updated Code
MyEntity myEntity = new MyEntity(); // You dont need to initalize
if(dtoToUpdate.getId() > 0) {
myEntity = (MyEntity) session.get(MyEntity.class, dtoToUpdate.getId())
}
myEntity.setName(dtoToUpdate.getName());
MyEntity result = this.myRepo.save(myEntity);
you are trying to save the myEntity , Which has recent information (correct version) from database. So you will not get any error. if you want to produce the error please do the following..
public MyEntity save(MyEntityDTO dtoToUpdate)
{
Session session = this.Session();
MyEntity myEntityV1 = null;
MyEntity myEntityV2 = null;
// Getting v1 and V2. at this time both V1 & V2 will have same version ( assume version as 5)
if(dtoToUpdate.getId() > 0) {
myEntityV1 = (MyEntity) session.get(MyEntity.class, dtoToUpdate.getId()) ;
myEntityV2 = (MyEntity) session.get(MyEntity.class, dtoToUpdate.getId()) ;
}
myEntityV1.setName(dtoToUpdate.getName());
// Saving V1 will reflect the increase in version ( actual row will be version of 6)
MyEntity result = this.myRepo.save(myEntityV1);
myEntityV2.setName("some changes"); // change some in V2instance. So that hibernate/Jpa will capture the change
this.myRepo.save(myEntityV2); // You will get exception. because the v2 has version as 5. but the row was updated .
this.repositoryManager.flush(myRepo);
}
So basically when you update the entity in Db, if the entity version (variable in object) is not equal to the version ( field in the table) in database ( before this update) it will throw exception
Related
I am writing a PUT request API with spring and mongodb. But the save() inserts a new object instead of update the current one.
#Document("Test")
public class Expense {
#Field(name = "name")
private String expenseName;
#Field(name = "category")
private ExpenseCategory expenseCategory;
#Field(name = "amount")
private BigDecimal expenseAmount;
public Expense( String expenseName, ExpenseCategory expenseCategory, BigDecimal expenseAmount) {
this.expenseName = expenseName;
this.expenseCategory = expenseCategory;
this.expenseAmount = expenseAmount;
}
public String getExpenseName() {
return expenseName;
}
public void setExpenseName(String expenseName) {
this.expenseName = expenseName;
}
public ExpenseCategory getExpenseCategory() {
return expenseCategory;
}
public void setExpenseCategory(ExpenseCategory expenseCategory) {
this.expenseCategory = expenseCategory;
}
public BigDecimal getExpenseAmount() {
return expenseAmount;
}
public void setExpenseAmount(BigDecimal expenseAmount) {
this.expenseAmount = expenseAmount;
}
}
This is my reporsitory class
public interface ExpenseRepository extends MongoRepository<Expense, String> {
}
This is my Service class which shows how to update the class.
#Service
public class ExpenseService {
private final ExpenseRepository expenseRepository;
public ExpenseService(ExpenseRepository expenseRepository) {
this.expenseRepository = expenseRepository;
}
public void updateExpense(String id, Expense expense){
Expense savedExpense = expenseRepository.findById(id)
.orElseThrow(() -> new RuntimeException(
String.format("Cannot Find Expense by ID %s", id)));
savedExpense.setExpenseName(expense.getExpenseName());
savedExpense.setExpenseAmount(expense.getExpenseAmount());
savedExpense.setExpenseCategory(expense.getExpenseCategory());
expenseRepository.save(savedExpense);
}
}
This is my controller
#RestController
#RequestMapping("/api/expense")
public class ExpenseController {
private final ExpenseService expenseService;
public ExpenseController(ExpenseService expenseService) {
this.expenseService = expenseService;
}
#PutMapping("/{id}")
public ResponseEntity<Object> updateExpense(#PathVariable String id, #RequestBody Expense expense){
expenseService.updateExpense(id, expense);
return ResponseEntity.ok().build();
}
}
As shown in mongodb compass, mongodb auto generates an _id field for every object. So I do not define a id field or use #id annotation to define a primary for the collection. However, in the service class, expenseRepository.findById(id) retrieves the desired object and update it. Why does save() do the insert instead of update? Many thanks.
JPA Can't find the existing entry as no id field id set. You need to add an id field and set generation type to auto.
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
This is maybe a beginner question on hibernate. I am doing my first steps, I designed a simple datamodel consisting of about 10 entities and I use hibernate to persist them to my Oracle XE database. Now I am facing the following problem: First time, when I do a transaction to persist some entities, they are persisted properly. I verify, that the data exists in the database and then I delete all the entries from all database tables. I verify that all tables are empty again. Then I run my program again to persist some new entities - and here happens something really strange: Afterwards I find in my databse the new entries as well as the old ones, which were persisted last time and which I had deleted! They contained the old IDs and the old data fields! How can this be? This happens even if I shut down my computer after the first time the program runs! How does it remember the old entries and where are they saved? Do you have any ideas?
Some information, that might be useful:
I am using annotations (instead of config files) for the mapping.
Following you see the classes used for persisting as well as one example of an entity (I am showing only one entity to avoid making the question too long).
As you see, I am using FetchType = EAGER on my MANY to MANY mappings (as I understand, this makes sure, that all related entities are loaded immediately together with any loaded entity). Can this have any impact?
Thanks for any help!
public class PersistenceManager {
private static final SessionFactory factory = new Configuration().configure().buildSessionFactory();
public static void sampleData() {
try(Session session = factory.openSession()) {
SampleDataLoader.loadSampleData(session);
} catch(HibernateException e) {
System.out.println("Exception during persisting! Message: " + e.getMessage());
e.printStackTrace();
}
}
}
public class SampleDataLoader {
static void loadSampleData(Session session) {
Language french = new Language("French");
Language german = new Language("German");
Noun garcon = new Noun(french, "garcon", false);
Noun junge = new Noun(german, "Junge", false);
junge.addTranslation(garcon);
ZUser user = new ZUser("Daniel", "password");
user.setOwnLanguage(german);
user.setEmail("abc#somemail.de");
user.setDateRegistered(LocalDateTime.now());
user.addForeignLanguage(french);
Transaction transaction = session.beginTransaction();
session.save(user);
session.save(french);
session.save(german);
session.save(junge);
transaction.commit();
}
}
#Entity
public class ZUser {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column
private String name;
#Column
private String password;
#Column
private String email;
#Column
private String picturePath;
#Column
private LocalDateTime dateRegistered;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="OWNLANGUAGE_ID")
private Language ownLanguage;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(name="USER_LANGUAGE",
joinColumns=#JoinColumn(name="USER_ID"),
inverseJoinColumns=#JoinColumn(name="LANGUAGE_ID")
)
private Set<Language> foreignLanguages = new HashSet<>();
public ZUser() { }
public ZUser(String n, String p) {
name = n;
password = p;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPicturePath() { return picturePath; }
public void setPicturePath(String picturePath) { this.picturePath = picturePath; }
public LocalDateTime getDateRegistered() { return dateRegistered; }
public void setDateRegistered(LocalDateTime dateRegistered) { this.dateRegistered = dateRegistered; }
public Language getOwnLanguage() { return ownLanguage; }
public void setOwnLanguage(Language ownLanguage) { this.ownLanguage = ownLanguage; }
public void addForeignLanguage(Language language) {foreignLanguages.add(language);}
public Set<Language> getForeignLanguages() {return Collections.unmodifiableSet(foreignLanguages); }
}
Clarified by the comment of Jagger (see comments). Indeed, I was using Oracle SQL command line to delete the entries and I had rgotten, that I need to explicitely commit after deleting. The solution can be so easy :)
Having a problem getting some data persisted with hibernate
I declare two independent classes, License and MacAddress. MacAddress can exist in its own right, but it can also be linked to to a license, when hibernate generates the underlying tables it creates a License, MacAddress and License_MacAddress table.
License
#Entity
public class License
{
#Id
#GeneratedValue
private Integer id;
#Column(nullable = false)
private String license;
#OneToMany(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
private
List<MacAddress> macAddresses;
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
public String getLicense()
{
return license;
}
public void setLicense(String license)
{
this.license = license;
}
public List<MacAddress> getMacAddresses()
{
return macAddresses;
}
public void setMacAddresses(List<MacAddress> macAddresses)
{
this.macAddresses = macAddresses;
}
}
MacAddress
#Entity
public class MacAddress
{
#Id
#GeneratedValue
private Integer id;
#Column(nullable = false)
private String macAddress;
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
public String getMacAddress()
{
return macAddress;
}
public void setMacAddress(String macAddress)
{
this.macAddress = macAddress;
}
}
What Im trying to do is the following.
At some point a new license is created and a row is stored in the License table (this works correctly)
Then at a later time the license, and macaddress of the user is received. The license is retrieved from the database , we check to see if the provided macAddress is already associated with the license (multiple macaddresses can be associated with the license) and if not we then want to store the macaddress and the link nbetween license and macaddress.
But despite setting CascadeType.ALL on the #OneToMany annotation of License, no Mac Address information get's stored. I can manually save the MacAddress by saving that object seperately but still the License_MacAddress table remains empty.
Code
session = Db.getSession();
License license = getLicense(session, licenseStr);
List<MacAddress> macAddresses = license.getMacAddresses();
for(MacAddress mac:macAddresses)
{
if(mac.getMacAddress().equals(macAddress))
{
return;
}
}
MacAddress mac = new MacAddress();
mac.setMacAddress(macAddress);
session.save(mac);
license.getMacAddresses().add(mac);
session.saveOrUpdate(license);
return;
Why can I not get the relationship between the two persisted ?
Hibernate follows Unit of Work pattern when saving changes to the database.
You open a unit of work, apply changes to objects associated with it and Hibernate automatically saves your changes when you close the unit of work.
In practical usage of Hibernate API the concept of Unit of Work usually matches Hibernate transaction, therefore you need a transaction in this case:
session = Db.getSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
License license = getLicense(session, licenseStr);
List<MacAddress> macAddresses = license.getMacAddresses();
for(MacAddress mac:macAddresses)
{
if(mac.getMacAddress().equals(macAddress))
{
return;
}
}
MacAddress mac = new MacAddress();
mac.setMacAddress(macAddress);
license.getMacAddresses().add(mac);
tx.commit();
} catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // or display error message
} finally {
session.close();
}
See also:
13.1.1. Unit of work
I am trying to insert a record in the database (using Java EE 6, EJB 3.1, JPA 2.0). I am getting an error that accountTypeId field is null, but i have set it up as autogenerate. Can anyone please suggest what am I doing wrong?
Following is the create table query:
create table example.account_type(
account_type_id INT NOT null PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
account_type_desc varchar(20)
);
Following is the entity class:
EDIT: Updated the entity class as generated by NetBeans which didn't work. I also added #GeneratedValue annotation but still it didn't work.
#Entity
#Table(name = "ACCOUNT_TYPE")
#NamedQueries({
#NamedQuery(name = "AccountType.findAll", query = "SELECT a FROM AccountType a"),
#NamedQuery(name = "AccountType.findByAccountTypeId", query = "SELECT a FROM AccountType a WHERE a.accountTypeId = :accountTypeId"),
#NamedQuery(name = "AccountType.findByAccountTypeDesc", query = "SELECT a FROM AccountType a WHERE a.accountTypeDesc = :accountTypeDesc")})
public class AccountType implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY) // ADDED THIS LINE
#Basic(optional = false)
#NotNull
#Column(name = "ACCOUNT_TYPE_ID")
private Integer accountTypeId;
#Size(max = 50)
#Column(name = "ACCOUNT_TYPE_DESC")
private String accountTypeDesc;
public AccountType() {
}
public AccountType(Integer accountTypeId) {
this.accountTypeId = accountTypeId;
}
public Integer getAccountTypeId() {
return accountTypeId;
}
public void setAccountTypeId(Integer accountTypeId) {
this.accountTypeId = accountTypeId;
}
public String getAccountTypeDesc() {
return accountTypeDesc;
}
public void setAccountTypeDesc(String accountTypeDesc) {
this.accountTypeDesc = accountTypeDesc;
}
#Override
public int hashCode() {
int hash = 0;
hash += (accountTypeId != null ? accountTypeId.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof AccountType)) {
return false;
}
AccountType other = (AccountType) object;
if ((this.accountTypeId == null && other.accountTypeId != null) || (this.accountTypeId != null && !this.accountTypeId.equals(other.accountTypeId))) {
return false;
}
return true;
}
#Override
public String toString() {
return "Entities.AccountType[ accountTypeId=" + accountTypeId + " ]";
}
}
Following is the session bean interface:
#Remote
public interface AccountTypeSessionBeanRemote {
public void createAccountType();
public void createAccountType(String accDesc);
}
Following is the session bean implementation class:
#Stateless
public class AccountTypeSessionBean implements AccountTypeSessionBeanRemote {
#PersistenceContext(unitName="ExamplePU")
private EntityManager em;
#Override
public void createAccountType(String accDesc) {
AccountType emp = new AccountType(accDsc);
try {
this.em.persist(emp);
System.out.println("after persist");
} catch(Exception ex) {
System.out.println("ex: " + ex.getMessage());
}
}
}
Following is the Main class:
public class Main {
#EJB
private static AccountTypeSessionBeanRemote accountTypeSessionBean;
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
accountTypeSessionBean.createAccountType("test");
}
}
Following is the error:
INFO: ex: Object: Entities.AccountType[ accountTypeId=null ] is not a known entity type.
You are not getting an error because of "accountTypeId field is null". As the error message says, the error occurs because "Entities.AccountType[ accountTypeId=null ] is not a known entity type".
The most likely reason is that AccountType is not annotated with #Entity. This problem is likely solved by adding it. Additionally it makes sense to use
#GeneratedValue(strategy = GenerationType.IDENTITY)
instead of AUTO. Auto means that the provider chooses a strategy based on the capabilities of the target database. According to the table definition it seems clear that the preferred strategy is IDENTITY.
I changed my create table query as following:
create table example.account_type(
account_type_id INT NOT null PRIMARY KEY,
account_type_desc varchar(20)
);
Then had to add the following line to the entity class (Netbeans doesn't add that):
#GeneratedValue(strategy = GenerationType.AUTO)
Hi I have created many to one relationship in hibernate.
Following is the code for that.
there are thousands of records present in B table which is link to single record of table A. When i used getBList() method it will returns thousands of record and JAVA goes OUT OF MEMORY.
So how can i solve this problem.
#Entity
#Table(name = "A")
public class A {
private int Id;
private String aName;
private List<MksReleaseInfo> bList;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
public int getId() {
return releaseId;
}
public void setId(final int Id) {
this.Id = Id;
}
#Column(name = "aname", unique = true)
public String getAName() {
return aName;
}
public void setAName(final String aName) {
this.aName = aName;
}
#OneToMany(mappedBy = "aName")
public List<MksReleaseInfo> getBList() {
return bList;
}
public void setBList(final List<B> bList) {
this.bList = bList;
}
}
#Entity
#Table(name = "B")
public class B {
private int bIndex;
private int bpriority;
private A aName;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
protected int getBIndex() {
return mksReleaseInfoIndex;
}
protected void setBIndex(final int bIndex) {
this.bIndex = bIndex;
}
#Column(name = "priority")
public int getBPriority() {
return bpriority;
}
public void setBPriority(final int bpriority) {
this.bpriority = bpriority;
}
#ManyToOne
#JoinColumn(name = "Id")
public A getAName() {
return aName;
}
public void setAName(final A aName) {
this.aName = aName;
}
}
after all the comments i have implemented the following code. but again it gives OUT OF MEMORY. Should i have to flush the memory explicitly and how?
public List<B> getList(String name, int offset, int limit) throws DAOException {
try {
String hql = "from B where name = :name";
begin();
Query query = getSession().createQuery(hql);
query.setString("name", name);
if(offset > 0){
query.setFirstResult(offset);
}
if(limit > 0){
query.setMaxResults(limit);
query.setFetchSize(limit);
}
commit();
return query.list();
} catch (HibernateException e) {
rollback();
}
}
public Long countB(String name) throws DAOException {
try {
String hql = "select count(*) from B where name = :name";
begin();
Query query = getSession().createQuery(hql);
query.setString("name", name);
commit();
return (Long)query.uniqueResult();
} catch (HibernateException e) {
rollback();
}
}
long count = countB(name);
int counter = (int) (count / 200);
if(count%200 > 0){
counter++;
}
for(int j = 0;j<counter;j++){
lists = getList(name, j*200, 200);
for(B count1 : lists){
System.out.println(count1);
}
}
You could introduce a DAO in order to retrieve the records from B given a A object in a paged way.
For example:
public interface BDao {
Page findByA(A a, PageRequest pageRequest);
}
Maybe you could take an idea from approach taked in Spring Data
Set MaxResults property of datasource, it will set limit on number of records you are getting.
Also, you can increase java heap memory size using -Xmx256m. This will set maximum heap allocation size to 256MB. You can set it as you need.
You can use query with paging for this purpose. In Query class you can find setFirstResult and setMaxResults methods which can help you to iterate over records. If you need to load all B objects and store them you can adjust memory settings of java by setting -Xmx option. Also you can try to declare some kind of reduced class B (for example ReducedB), which contains only required fields, and use iterating with converting B to ReducedB to reduce memory usage.
Also you can check this question. I think that it is close enought to what you want.
P.S. Final solution would depend on particular issue that you want to solve.
I had the same issue. I looked at my code and server space but nothing helped. Later I looked into data and realized wrongly placed data was making application use lot of processing power. Make sure you do not have duplicated data in child class.