I am trying to create a project that will use Hibernate to store the objects to the database.
If I simply insert (save) an object that does not contain a mapping with another table everything works fine. However, I have a case where there is a connection between three tables. The tables are the Asset, MonetaryValue and CurrencyType (see below).
When an Asset is inserted, the monetaryValueType must be provided (by the user ) along with the currency type. Asset holds a OneToOne relation with the MonetaryValueType and MonetaryValueType holds a OneToOne relation to the CurrencyType Table.
More specifically, below you will find the database tables.
Asset(asset_id,ownerIID,valueID,samID), where valueID is the foreign key to the MonetaryValueType Table (OneToOne undirectional mapping)
MonetaryValueType(mvID, mValue,currencyId), where currencyID is the foreign key to the CurrencyType Table (OneToOne undirectional mapping)
CurrencyType(currencyID,currField,currValue,currSymbol).
The problem is that every time I create the asset object and I am calling the asset service to save the element, Hibernate either create a select query that tries to select from a database table I did never define or Inserts in the currency field with wrong column names (i.e. currency_field instead of currField etc.)
I've tried to play with all the Cascade types but nothing seems to work.
Asset.java
#Entity
#Table(name="asset")
public class Asset implements java.io.Serializable{
#Id
#Column(name="assetID", unique = true, nullable = false)
private long assetID;
#Column(name="ownerID")
private long ownerID;
#OneToOne
#JoinColumn(name="valueID")
private MonetaryValueType monetaryValueType;
#Column(name="samID")
private long samID;
------------Constructor, Getters , Setters-----
MonetaryValueType.java
#Entity
#Table(name="monetaryvaluetype")
public class MonetaryValueType{
#Id
#Column(name="mvID",nullable = false,unique = true)
private Long id;
#Column(name="mValue")
private double mValue;
#OneToOne
#JoinColumn(name="currencyId")
private CurrencyType currency;
------------Constructor, Getters , Setters-----
CurrencyType.java
#Entity
#Table(name="currencytype")
public class CurrencyType implements java.io.Serializable {
#Id
#Column(name="currencyID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int currencyID;
#Column(name="currField")
private String currField;
#Column(name="currValue")
private String currValue;
#Column(name="currSymbol")
private String currSymbol;
------------Constructor, Getters , Setters-----
Every entity holds its own DAO,DAOImpl, Service and ServiceImpl class. For instance, for the asset class the DAOImpl and ServiceImpl can be found below:
AssetDAOImpl.java
#Repository
public class AssetDAOImpl implements AssetDAO{
private Logger logger = LoggerFactory.getLogger(this.getClass());
//entity manager field
#Autowired
private EntityManager entityManager;
#Override
public List<Asset> findAll() {
Session currentSession = entityManager.unwrap(Session.class);
//create a query
Query theQuery =
currentSession.createQuery("from asset",Asset.class);
//execute query and get result list
List<Asset> aModelElements = theQuery.getResultList();
//return the results
return aModelElements;
}
#Override
public Asset findById(int theId) {
return null;
}
#Override
public Asset insert(Asset assetElement) {
//Session currentSession = entityManager.unwrap(Session.class);
boolean success = false;
try {
entityManager.persist(assetElement);
logger.info("Asset -> {}", assetElement);
return assetElement;
}
catch(Exception e){
e.printStackTrace();
}
return null;
}
AssetServiceImpl.java
#Service
public class AssetServiceImpl implements AssetService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private AssetDAO assetDAO;
#Autowired
public AssetServiceImpl(AssetDAO theAssetDAO){
assetDAO=theAssetDAO;
}
#Override
#Transactional
public List<Asset> findAll() {
return assetDAO.findAll();
}
#Override
#Transactional
public Asset findById(int theId) {
return assetDAO.findById(theId);
}
#Override
#Transactional
public Asset insert(Asset theAsset) {
assetDAO.insert(theAsset);
return theAsset;
}
...
The class that I use to fill the asset class (and all its children) is:
UniqueIDGenerator uniqueIDGenerator = new UniqueIDGenerator();
CurrencyType currencyType = new CurrencyType();
Asset asset = new Asset();
MonetaryValueType monetaryValueType = new MonetaryValueType();
currencyType.setCurrValue(ctx.value().monetaryValueType().currency().CurrencyType().getText());
currencyType.setCurrSymbol("currency");
monetaryValueType.setId(uniqueIDGenerator.nextId());
monetaryValueType.setmValue(Double.parseDouble(ctx.value().monetaryValueType().mValue().getText()));
monetaryValueType.setCurrency(currencyType);
asset.setMonetaryValueType(monetaryValueType);
asset.setAssetID(uniqueIDGenerator.nextId());
asset.setOwner(uniqueIDGenerator.nextId());
asset.setSamID(uniqueIDGenerator.nextId());
assetService.insert(asset);
Whenever I call the class mentioned above, I get the following error:
Hibernate:
insert
into
element1
(datefrom, dateto, description, name, statusid, samid)
values
(?, ?, ?, ?, ?, ?)
2019-08-05 20:19:00 INFO MyClass:63 - the result is:true
Hibernate:
select
monetaryva_.mvid,
monetaryva_.currency_id as currency3_57_,
monetaryva_.m_value as m_value2_57_
from
monetaryvaluetype monetaryva_
where
monetaryva_.mvid=?
2019-08-05 20:19:01.084 WARN 56712 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1054, SQLState: 42S22
2019-08-05 20:19:01.084 ERROR 56712 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : Unknown column 'monetaryva_.currency_id' in 'field list'
As you can see, hibernate created columns (currency_id instead of currencyID) that are not in accordance with my database tables even though I used the #Column annotation.
Use following two lines in your application.properties file
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
Related
I have a Spring boot Entity defined as :
#Data
#Entity
#Table(name = "TaxOffice")
public class TaxOffice {
public TaxOffice(){}
public TaxOffice(int id, String name, int voivodeship_id){
this.id = id;
this.name = name;
this.voivodeship_id = voivodeship_id;
}
#Id
private int id;
#Column(name="name")
private String name;
#Column(name="voivodeship_id")
private int voivodeship_id;
#ManyToOne
#JoinColumn(name = "city_id")
private City city;
#OneToOne
#JoinColumn(name = "details_id")
private TaxOffice_Detail taxOffice_details;
}
In application-test.properties, I have following settings:
spring.datasource.url=jdbc:h2:mem:TestDB;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.defer-datasource-initialization=true
When I run this Test
#Test
void findAllByCity_idTest(){
assertEquals(1, taxOfficeService.findAllByCity_id(48).size());
}
i recieve this error:
org.h2.jdbc.JdbcSQLSyntaxErrorException:
Table "TAX_OFFICE" not found; SQL statement:
/* select t from TaxOffice t where t.city.id = :id */ select taxoffice0_.id as id1_1_, taxoffice0_.city_id as city_id4_1_, taxoffice0_.name as name2_1_, taxoffice0_.details_id as details_5_1_, taxoffice0_.voivodeship_id as voivodes3_1_ from tax_office taxoffice0_ where taxoffice0_.city_id=? [42102-210]
There is no Table "TAX_OFFICE", but there is "TaxOffice", so why is it looking for "TAX_OFFICE"?
Why is this happening and how can i fix this?
Edit: TaxService.java
#Transactional
#Service
public class TaxOfficeService {
#Autowired
TaxOfficeRepository taxOfficeRepository;
public List<TaxOffice> findAllByCity_id(int id){
return taxOfficeRepository.findAllByCity_id(id);
}
}
TaxOfficeRepository
#Repository("taxOfficeRepository")
public interface TaxOfficeRepository extends JpaRepository<TaxOffice,Integer> {
#Query("select t from TaxOffice t where t.city.id = :id")
List<TaxOffice> findAllByCity_id(int id);
}
Hibernate and Spring by default having naming strategies, which decide how the entity class must be compiled and the table and column names be generated. This can be customized as per use through application properties or hibernate configuration file.
eg
spring:
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
I'm working on adding a feature to an already developed spring boot web application. The primary entity that has child entities is a Record. It has a few columns/variables that I want to now be in its own, separate entity (CustomerOrder) and exist in a one-to-one relationship with the Record. To summarize:
Record {
thing 1
thing 2
thing 3
}
is now becoming:
CustomerOrder {
thing 1
thing 2
thing 3
}
Record {
CustomerOrder
}
I'm having some issues with what I've produced. Here is the CustomerOrder model's relevant relationship data:
#Entity
#Table(name="customer_orders")
public class CustomerOrder {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
... other columns
#OneToOne(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy="customerOrder", fetch = FetchType.EAGER)
private Record record;
}
And then here is the Record model's relevant data:
#Entity
#Table(name="records")
public class Record extends Auditable<String> implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
... other columns
#OneToOne
#JoinColumn(name="customer_order_id", nullable = false, unique = true)
private CustomerOrder customerOrder;
}
My issue exists when I try to POST a record, when a user tries creating one in the ui. Here is the POST method for a record:
#PostMapping
public ResponseEntity<?> saveRecord(#RequestBody Record recordBody, BindingResult result) {
if(!result.hasErrors()) {
if(recordBody.getHardwareItems().isEmpty()) {
record = recordsService.save(recordBody);
} else {
// Save the record first, recordId is required on hardwareItems
// TODO: investigate Spring Hibernate/JPA rules - is there a way to save parent before children to avoid a null recordId
CustomerOrder customerOrder = recordBody.getCustomerOrder();
recordBody.setCustomerOrder(new CustomerOrder());
customerOrder.setRecord(record);
customerOrder = customerOrdersService.save(customerOrder);
record = recordsService.save(recordBody);
}
} else {
return new ResponseEntity<>(result.getAllErrors(), HttpStatus.BAD_REQUEST);
}
// Return the location of the created resource
uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{recordId}").buildAndExpand(record.getId()).toUri();
return new ResponseEntity<>(uri, HttpStatus.CREATED);
}
The error I receive is the following:
2021-02-19 00:46:28.398 WARN 29990 --- [io-8080-exec-10] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1364, SQLState: HY000
2021-02-19 00:46:28.398 ERROR 29990 --- [io-8080-exec-10] o.h.engine.jdbc.spi.SqlExceptionHelper : Field 'record_id' doesn't have a default value
This makes sense to me at least, since I'm trying to save the CustomerOrder object that depends on a Record object, which has yet to have been persisted. So, how do I go about changing up the order and/or creating and persisting a Record object so that I can then save the CustomerOrder object to it?
You need to mark your column record_id as AI(AUTO_INCREMENT) in your table definition.
ALTER TABLE records CHANGE record_id INT(6) NOT NULL AUTO_INCREMENT;
Your primary key is record_id, add #Column(name = "record_id", nullable = false)
#Entity
#Table(name="records")
public class Record extends Auditable<String> implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "record_id", nullable = false)
private Long id;
... other columns
#OneToOne
#JoinColumn(name="customer_order_id", nullable = false, unique = true)
private CustomerOrder customerOrder;
}
I ve created a table in Postgres and do a relation in java code :
#Getter
#Setter
#Entity
#Table(name = "resources")
public class Resource {
#Id
#Column(name = "raport_id")
private BigDecimal raportId;
#Column(name = "reference_id")
private String referenceId;
}
Also I created a repository
public class ResourcesRepository {
#PersistenceContext
private EntityManager entityManager;
public void persist(BigDecimal raportId, String referenceId, String type) {
Resource resource = new Resource();
resource.setRaportSysId(raportId);
resource.setReferenceId(referenceId);
entityManager.persist(raport);
}
public void updateRaportId(BigDecimal raportId) {
entityManager.createQuery("UPDATE Resource r set r.raportId = :raportId ")
.setParameter("raportId", raportId)
.executeUpdate();
}
}
I am passing the parameter raportId in dofferent location invoking the query like this:
ResourcesRepository.updateRaportId(raport.getId());
I do not have any errors but the table is not populated. Wonder what I am doing wrong? Should I use Insert INTO instead of update?
make sure you commit your transaction after update/persist
Given this entity class
#Entity
public class Profile extends Model {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
...
#OneToMany(fetch=FetchType.EAGER)
#Cascade(value={CascadeType.ALL})
#Fetch(FetchMode.SELECT)
private Set<Expectation> expectations;
...
}
the update operation made on the Profile object does not affect the expectations set (mapped on a join table).
I use the following code:
Session dbSession = DBHandler.getDbSession(); // custom static method
Profile profile = (Profile) dbSession.get(Profile.class, id);
Set<Expectation> expectations = new HashSet<Expectation>();
for (Long id : expectationsIds)
expectations.add((Expectation) dbSession.get(Expectation.class, id));
profile.setExpectations(expectations);
dbSession.beginTransaction();
dbSession.saveOrUpdate(profile);
dbSession.getTransaction().commit();
dbSession.close();
Currently I upload a CSV file which contains the following columns to the Google App Engine datastore:
id, roleName, eggName, highestAllTimePrice, lowestAllTimePrice, averagePrice
And the result of the table when I go to localhost:8888/_ah/admin/datastore are as follows:
Key, Write Ops, ID/Name, averagePrice, eggName, eggNumber, highestAllTimePrice, lowestAllTimePrice, roleName
I get all of the data from the table as follows:
public String GetData(final ModelMap model)
{
EntityManager em = EMF.get().createEntityManager();
Query q = em.createQuery("SELECT s FROM Egg s");
List<Egg> eggs = new ArrayList<Egg>(q.getResultList());
model.addAttribute("eggs", eggs);
return "index";
}
Entity
#Entity
public class Egg {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
private int eggNumber;
private String roleName;
private String eggName;
private double highestPrice;
private double lowestPrice;
private double averagePrice;
// getters setters
}
But when I run that query, I get this error:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Double
at com.google.appengine.datanucleus.FetchFieldManager.fetchDoubleField(FetchFieldManager.java:140)
at org.datanucleus.state.AbstractStateManager.replacingDoubleField(AbstractStateManager.java:2256)
at Entity.Egg.jdoReplaceField(Egg.java)
at Entity.Egg.jdoReplaceFields(Egg.java)
The problem is I don't know why it's converting types? Have I mapped my entity class wrong?
Perhaps your double fields (highestPrice, etc) are considered to be strings in the datastore, and thus cannot be mapped to your entity.