Querying with ManyToOne association results in SQLException - java

I am trying out a simple use case for many-to-one association with Spring Data JPA but it is resulting in an SQLException.
Model is of typical Order and OrderItem classes.
Order
#Entity
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
OrderItem
#Entity
public class OrderItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "fk_order")
private Order order;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
}
Unit test
#SpringBootTest
public class MappingTests {
#Autowired
private OrderRepository orderRepository;
#Autowired
private OrderItemRepository orderItemRepository;
#Test
#Transactional
#Disabled
public void testManyToOneUnidirectional() {
Order order = new Order();
order.setName("order-01");
orderRepository.save(order);
OrderItem orderItem = new OrderItem();
orderItem.setName("item-01");
orderItem.setOrder(order);
orderItemRepository.save(orderItem);
// List<OrderItem> all = orderItemRepository.findAll();
}
Unit test passes. But if I uncomment the line to find all items from OrderItemRepository it fails with following exception:
2020-11-17 11:37:07.671 WARN 35823 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 42001, SQLState: 42001
2020-11-17 11:37:07.671 ERROR 35823 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Syntax error in SQL statement "INSERT INTO ORDER[*] (NAME, ID) VALUES (?, ?)"; expected "identifier"; SQL statement:
insert into order (name, id) values (?, ?) [42001-200]
2020-11-17 11:37:07.685 INFO 35823 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext#6c61a903 testClass = MappingTests, testInstance = com.example.jpademo.MappingTests#5408d4b3, testMethod = testManyToOneUnidirectional#MappingTests, testException = org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [insert into order (name, id) values (?, ?)]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement, mergedContextConfiguration = [WebMergedContextConfiguration#658c5a19 testClass = MappingTests, locations = '{}', classes = '{class com.example.jpademo.JpaDemoApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer#66d18979, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer#17f7cd29, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer#0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer#7ee8290b, org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizer#2e377400, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer#e4487af, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer#0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer#683dbc2c, org.springframework.boot.test.context.SpringBootTestArgs#1, org.springframework.boot.test.context.SpringBootTestWebEnvironment#233c0b17], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]
org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [insert into order (name, id) values (?, ?)]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:259)
What could be missing here ?
Thanks.

While schema is created by hibernate, it seems to fail with following error if comes across table named 'order'. I renamed the table to some other name and it worked as expected. I think hibernate identifies order as keyword rather than entity name.
2020-11-17 16:14:51.097 WARN 126490 --- [ task-1] o.h.t.s.i.ExceptionHandlerLoggedImpl : GenerationTarget encountered exception accepting command : Error executing DDL "drop table if exists order CASCADE " via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "drop table if exists order CASCADE " via JDBC Statement

I had this same issue a while back.
Hibernate is trying to execute "drop table if exists order CASCADE " but ORDER is a keyword, and your table name is also ORDER. If you can add #Entity(name = "Orders") to your order entity. Your error should get fixed.

Related

Spring JPA H2 database get org.h2.jdbc.JdbcSQLSyntaxErrorException Table not found

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

Hibernate - Mapping three tables with a single save

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

JPA update record is failing due to #OneToMany

I have a very small doubt regarding #OneToMany mapping.
I have a model student and another model attendance.
A student can have multiple attendance. but student model should only be able to retrieve the attendance info.
But when I am trying to change some student info I am getting below error as it is trying to update attendance record.
here is my mapping
#Entity
#Table(name="student_detail")
#Getter #Setter
public class StudentDetailsModel {
#Id
#Column(name="reg_no",updatable = false, nullable = false)
private String regNo;
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
#JoinColumn(name = "reg_no")
private List<AttendanceModel> attendances;
}
and the exception I a getting.
update
student_detail
set
address=?,
alt_contact_number=?,
blood_group=?,
contact_number=?,
dob=?,
father_name=?,
first_name=?,
gender=?,
last_name=?,
middle_name=?,
mother_name=?,
photo_url=?,
school_id=?
where
reg_no=?
Hibernate:
update
attendance
set
reg_no=null
where
reg_no=?
2019-01-13 12:12:52.922 WARN 10708 --- [nio-8081-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 23502
2019-01-13 12:12:52.923 ERROR 10708 --- [nio-8081-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: null value in column "reg_no" violates not-null constraint
Detail: Failing row contains (null, 1, 2019-01-05, t, 2).
2019-01-13 12:12:52.926 INFO 10708 --- [nio-8081-exec-1] o.h.e.j.b.internal.AbstractBatchImpl : HHH000010: On release of batch it still contained JDBC statements
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [reg_no]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
my attenance model is as follows
#Entity
#Table(name="attendance")
#Getter #Setter
public class AttendanceModel {
//#EmbeddedId
//private AttendanceId attendanceId;
#Id
#Column(name="attendance_id")
private long id;
#Column(name="reg_no")
private String regNo;
#Column(name="subject_id")
private long subjectId;
#Column(name="class_date")
private Date classDate;
#Column(name="present")
private boolean present;
}
Could you show me Student Model.If i look your code post : You using Unidirectional relationship.
I think it must :
#OneToMany(fetch = FetchType.LAZY , cascade = CascedeType.ALL)
#JoinColumn(name="attendance_id")
private List<AttendanceModel> attendances = new ArrayList<>();

Why to use Set in OneToMany Mapping in hibernate

I have two tables with a one-to-many relationship. I want to fetch those records and insert into another database which having same table by changing the primary key.
My application entity class
#Entity
#Table(name = "EM_APPLICATION")
public class ApplicationTable {
#Id
private int APPLICATION_ID;
#Id
private String CUSTOMER_ID;
private String LAST_NAME;
private String FIRST_NAME;
#OneToMany( fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinColumns({ #JoinColumn(name = "CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID"),
#JoinColumn(name = "APPLICATION_ID", referencedColumnName = "APPLICATION_ID") })
private Set<AddressTable> address;
//Getters and setters
}
Address entity class..
#Entity
#Table(name="EM_APPL_ADDRESS")
public class AddressTable{
#Id
private int APPLICATION_ID;
#Id
private String CUSTOMER_ID;
#Id
private String ADDRESS_TYPE;
//Getters and setters
}
I have to execute a method for fetching records from DB using hibernate:
public void execute(String applId, String customerId) {
Session session = HibernateQAUtil.openSession();
Transaction tx = session.beginTransaction();
String hql = "FROM ApplicationTable WHERE CUSTOMER_ID =:CUSTOMER_ID AND APPLICATION_ID =:APPLICATION_ID";
Query query = session.createQuery(hql);
query.setParameter("CUSTOMER_ID", customerId);
query.setParameter("APPLICATION_ID", Integer.parseInt(applId));
List<ApplicationTable> list = query.list();
tx.commit();
session.close();
ApplicationTable applVO = list.get(0);
insertApplication(applVO );
}
After fetching the records, I am changing APPLICATION_ID, CUSTOMER_ID and some other columns in address table and after inserting in another database.
private void insertApplication(ApplicationTable emApplVO) {
applVO.setAPPLICATION_ID(123456);
applVO.setCUSTOMER_ID("88888888");
Set<AddressTable> addressSet = emApplVO.getAddress();
for (AddressTable address : addressSet) {
address.setAPPLICATION_ID(123456);
address.setCUSTOMER_ID("88888888");
address.setZIP(500032);
}
Session session1 = HibernateUtil.openSession();
Transaction beginTransaction = session1.beginTransaction();
session1.save(emApplVO);
beginTransaction.commit();
session1.close();
}
Hibernate queries in console log are... (below mentioned queries are too large so copied to some extent only..)
Hibernate: select em_applica0_.CUSTOMER_ID as CUSTOMER1_0_,em_applica0_.APPLICATION_ID as APPLICAT2_0_,em_applica0_.ARCHIVE_IND as ARCHIVE8_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=?
Hibernate: select address0_.CUSTOMER_ID as CUSTOMER1_0_1_, address0_.APPLICATION_ID as APPLICAT2_0_1_, address0_.ADDRESS_TYPE as ADDRESS3_1_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=?
Hibernate: insert into EM_APPLICATION (CUSTOMER_ID, APPLICATION_ID, APPLICATION_NBR, APPLICATION_STATUS, APPLICATION_TYPE) values (?, ?, ?, ?)
Hibernate: insert into EM_APPL_ADDRESS (CUSTOMER_ID, APPLICATION_ID, ADDRESS_TYPE) values (?, ?, ?)
Question 1: in the insert method, I have assigned address to addresSet and made some changes in addresSet, after making those changes, I am not assigned the addressSet to applVO (i.e. not written applVO.setAddress(addresSet )) but it inserted a record with updated values into the Address table. What is happening here?
When I am changing code inside insertApplication(ApplicationTable emApplVO) method to
private void insertApplication(ApplicationTable emApplVO) {
applVO.setAPPLICATION_ID(123456);
applVO.setCUSTOMER_ID("88888888");
Set<AddressTable> addressSet = emApplVO.getAddress();
Set<AddressTable> newAddressSet = new HashSet<AddressTable>();
for (AddressTable address : newAddressSet) {
address.setAPPLICATION_ID(emApplVO.getAPPLICATION_ID());
address.setCUSTOMER_ID(emApplVO.getCUSTOMER_ID());
address.setZIP(500032);
newAddressSet.add(address);
}
emApplVO.setAddress(null);
emApplVO.setAddress(newAddressSet);
Session session1 = HibernateUtil.openSession();
Transaction beginTransaction = session1.beginTransaction();
session1.save(emApplVO);
beginTransaction.commit();
session1.close();
}
Hibernate queries in console log are... It also executing update ...
Hibernate: select em_applica0_.CUSTOMER_ID as CUSTOMER1_0_,em_applica0_.APPLICATION_ID as APPLICAT2_0_,em_applica0_.ARCHIVE_IND as ARCHIVE8_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=?
Hibernate: select address0_.CUSTOMER_ID as CUSTOMER1_0_1_, address0_.APPLICATION_ID as APPLICAT2_0_1_, address0_.ADDRESS_TYPE as ADDRESS3_1_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=?
Hibernate: insert into EM_APPLICATION (CUSTOMER_ID, APPLICATION_ID, APPLICATION_NBR, APPLICATION_STATUS, APPLICATION_TYPE) values (?, ?, ?, ?)
Hibernate: insert into EM_APPL_ADDRESS (CUSTOMER_ID, APPLICATION_ID, ADDRESS_TYPE) values (?, ?, ?)
update EM_APPL_ADDRESS set CUSTOMER_ID=?, APPLICATION_ID=? where CUSTOMER_ID=? and APPLICATION_ID=? and ADDRESS_TYPE=?
Question 2: why is the update query executed?
Question 3: while using List<AddressTable> instead of Set<AddressTable>, I got some errors. What is the difference?

JPA / Hibernate "simple" OneToMany Unidirictional / Clear of set does not work

I don't know what I am doing wrong, but apparently I am not able to create a simple OneToMany relationship with hibernate.
Here are my tables how they look in DB:
I will only show relevant part, so the question does not get to bloated.
My User Looks like
#Entity(name = "CORE_USER")
public class User extends AbstractPersistentObject {
...
#ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
#JoinTable(name = "CORE_USER_TO_ROLE",
joinColumns = { #JoinColumn(name = "USER_ID") },
inverseJoinColumns = { #JoinColumn(name = "ROLE_ID") })
private Set<UserRole> roles = new HashSet<UserRole>();
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID")
private Set<UserRoleParam> userRoleParams = new HashSet<UserRoleParam>();
...(getter and setters)
}
Here Core user role param entity
#Entity(name = "CORE_USER_ROLE_PARAM")
public class UserRoleParam extends AbstractPersistentObject {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ROLE_ID")
private UserRole userRole;
#Column(name = "ROLE_PARAMETER")
private String paramter;
...(getter and setter)....
}
UserRole entity
#Entity(name="CORE_USER_ROLE")
public class UserRole extends AbstractPersistentObject {
#Enumerated(EnumType.STRING)
#Column(name = "ROLE_NAME", length = 30, nullable=false, unique=true)
private UserRoleEnum roleName;
...(getter and setters)
}
When I do this in my test:
#Test
#Transactional(propagation = Propagation.NEVER)
public void saveUserRoleParametersTest() throws Exception {
// load an user which exists and check params is zero
UserDTO userDTO = userService.getUserDTO(Users.DE678_ACTIVE_ALLROLES.getObject().getId());
Assert.assertNotNull(userDTO);
Assert.assertNotNull(userDTO.getUserRoleParams());
Assert.assertEquals(0, userDTO.getUserRoleParams().size());
Map<UserRoleEnum, List<String>> userRoleParams = new HashMap<>();
userRoleParams.put(UserRoleEnum.BASIC, new ArrayList<>());
userRoleParams.get(UserRoleEnum.BASIC).add("BASIC_PARAM");
// save params to user
userService.saveUserRoleParameters(Users.DE678_ACTIVE_ALLROLES.getObject().getId(), userRoleParams);
userDTO = userService.getUserDTO(Users.DE678_ACTIVE_ALLROLES.getObject().getId());
Assert.assertNotNull(userDTO);
Assert.assertNotNull(userDTO.getUserRoleParams());
Assert.assertEquals(1, userDTO.getUserRoleParams().size());
Assert.assertEquals(1, userDTO.getUserRoleParams().get(UserRoleEnum.BASIC).size());
Assert.assertTrue(userDTO.getUserRoleParams().get(UserRoleEnum.BASIC).contains("BASIC_PARAM"));
// delete params of user
userService.saveUserRoleParameters(Users.DE678_ACTIVE_ALLROLES.getObject().getId(), null);
userDTO = userService.getUserDTO(Users.DE678_ACTIVE_ALLROLES.getObject().getId());
Assert.assertNotNull(userDTO);
Assert.assertNotNull(userDTO.getUserRoleParams());
Assert.assertEquals(0, userDTO.getUserRoleParams().size());
}
here is also the user service method which I invoke:
#Override
public void saveUserRoleParameters(final String userId, final Map<UserRoleEnum, List<String>> userRoleParams) throws UserNotFoundException {
User user = userDAO.get(userId);
if (user == null) {
throw new UserNotFoundException(userId);
}
if (userRoleParams == null || userRoleParams.isEmpty()) {
user.getUserRoleParams().clear();
} else {
List<UserRole> roles = userDAO.getUserRolesByEnums(userRoleParams.keySet());
Map<UserRoleEnum, UserRole> enumToEntity = new HashMap<>();
roles.stream().forEach(r -> enumToEntity.put(r.getRoleName(), r));
for (Entry<UserRoleEnum, List<String>> entry : userRoleParams.entrySet()) {
UserRoleParam urp = new UserRoleParam(enumToEntity.get(entry.getKey()), entry.getValue().stream().collect(Collectors.joining(";")));
user.getUserRoleParams().add(urp);
}
}
userDAO.saveOrUpdate(user);
}
The problem is that my test fails on first service method call of saveUserRoleParameters which is (EDIT: now with sql log enabled):
DEBUG [main] 12.05.17 08:46:50.264 org.hibernate.engine.jdbc.spi.SqlStatementLogger#logStatement: select user0_.id as id1_0_0_, user0_.version as version2_0_0_, user0_.ACTIVE as ACTIVE1_38_0_, user0_.APP_LANG as APP_LANG2_38_0_, user0_.DEFAULT_MODULE as DEFAULT_3_38_0_, user0_.ORGA_UNIT as ORGA_UNI4_38_0_, user0_.USER_FULL_NAME as USER_FUL5_38_0_, user0_.USER_NAME as USER_NAM6_38_0_ from CORE_USER user0_ where user0_.id=?
DEBUG [main] 12.05.17 08:46:50.270 org.hibernate.engine.jdbc.spi.SqlStatementLogger#logStatement: select userrole0_.id as id1_0_, userrole0_.version as version2_0_, userrole0_.ROLE_NAME as ROLE_NAM1_41_ from CORE_USER_ROLE userrole0_ where userrole0_.ROLE_NAME in (?)
DEBUG [main] 12.05.17 08:46:50.287 org.hibernate.engine.jdbc.spi.SqlStatementLogger#logStatement: select userrolepa0_.USER_ID as USER_ID3_0_0_, userrolepa0_.id as id1_42_0_, userrolepa0_.id as id1_0_1_, userrolepa0_.version as version2_0_1_, userrolepa0_.ROLE_PARAMETER as ROLE_PAR1_42_1_, userrolepa0_.ROLE_ID as ROLE_ID2_42_1_ from CORE_USER_ROLE_ROLE_PARAM userrolepa0_ where userrolepa0_.USER_ID=?
DEBUG [main] 12.05.17 08:46:50.290 org.hibernate.engine.jdbc.spi.SqlStatementLogger#logStatement: insert into CORE_USER_ROLE_PARAM (version, ROLE_PARAMETER, ROLE_ID, id) values (?, ?, ?, ?)
WARN [main] 12.05.17 08:46:50.291 org.hibernate.engine.jdbc.spi.SqlExceptionHelper#logExceptions: SQL Error: 23502, SQLState: 23502
ERROR [main] 12.05.17 08:46:50.291 org.hibernate.engine.jdbc.spi.SqlExceptionHelper#logExceptions: NULL nicht zulässig für Feld "USER_ID"
NULL not allowed for column "USER_ID"; SQL statement:
insert into CORE_USER_ROLE_ROLE_PARAM (version, ROLE_PARAMETER, ROLE_ID, id) values (?, ?, ?, ?) [23502-175]
WARN [main] 12.05.17 08:46:50.291 org.hibernate.engine.jdbc.spi.SqlExceptionHelper#logExceptions: SQL Error: 23502, SQLState: 23502
ERROR [main] 12.05.17 08:46:50.292 org.hibernate.engine.jdbc.spi.SqlExceptionHelper#logExceptions: NULL nicht zulässig für Feld "USER_ID"
NULL not allowed for column "USER_ID"; SQL statement:
Shouldn't jpa put the UserId where it belongs? What I want is a Unidirectional relationship where User knows about UserRoleParams but not the other way around. Like in the example here http://www.objectdb.com/api/java/jpa/annotations/relationship
EDIT#2:
I found a solution. I added following on User Entity:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID", nullable = false)
private Set<UserRoleParam> userRoleParams = new HashSet<UserRoleParam>();
Now I have the issue that clearing the set will not be persisted. My test fails as the second check if the Set is empty fails. It shows that the Parameters are still set.
The fields User.userRoleParams and UserRoleParam.user are part of a bidirectional relation. To do that you must add
mappedBy="user" to the #OneToMany side.
You cannot have those two parts as independent relations (i.e unidirectional 1-N and unidirectional N-1) reusing the same FK column ("USER_ID").
As for your documentation you linked to in the comments, the only use of unidirectional 1-N has no field on the other side (and you do have a field on the other side of the relation).
User has changed their question since this answer! Why do I bother?

Categories

Resources