Spring-MVC, Hibernate : Creating DTO objects from Domain objects - java

I am working on a Spring-MVC application in which I am trying to search for List of GroupNotes in database. The mapping in my project is GroupCanvas has one-to-many mapping with GroupSection and GroupSection has one-to-many mapping with GroupNotes. Because of these mappings, I was getting LazyInitializationException. As suggested on SO, I should be converting the objects to a DTO objects for transfer. I checked on net, but couldnt find a suitable way to translate those.
I have just created a new List to avoid the error, but one field is still giving me an error. I would appreciate if anyone tells me either how to fix this error or convert the objects to a DTO objects so they can be transferred.
Controller code :
#RequestMapping(value = "/findgroupnotes/{days}/{canvasid}")
public #ResponseBody List<GroupNotes> findGroupNotesByDays(#PathVariable("days")int days, #PathVariable("canvasid")int canvasid){
List<GroupNotes> groupNotesList = this.groupNotesService.findGroupNotesByDays(days,canvasid);
List<GroupNotes> toSendList = new ArrayList<>();
for(GroupNotes groupNotes : groupNotesList){
GroupNotes toSendNotes = new GroupNotes();
toSendNotes.setMnotecolor(groupNotes.getMnotecolor());
toSendNotes.setNoteCreationTime(groupNotes.getNoteCreationTime());
toSendNotes.setMnotetag(groupNotes.getMnotetag());
toSendNotes.setMnotetext(groupNotes.getMnotetext());
toSendNotes.setAttachCount(groupNotes.getAttachCount());
toSendNotes.setNoteDate(groupNotes.getNoteDate());
toSendList.add(toSendNotes);
}
return toSendList;
}
GroupNotesDAOImpl :
#Override
public List<GroupNotes> searchNotesByDays(int days, int mcanvasid) {
Session session = this.sessionFactory.getCurrentSession();
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -days);
long daysAgo = cal.getTimeInMillis();
Timestamp nowMinusDaysAsTimestamp = new Timestamp(daysAgo);
GroupCanvas groupCanvas = (GroupCanvas) session.get(GroupCanvas.class,mcanvasid);
Query query = session.createQuery("from GroupSection as n where n.currentcanvas.mcanvasid=:mcanvasid");
query.setParameter("mcanvasid", mcanvasid);
List<GroupSection> sectionList = query.list();
List<GroupNotes> notesList = new ArrayList<GroupNotes>();
for (GroupSection e : sectionList) {
System.out.println("Section name is "+e.getMsectionname());
GroupSection groupSection = (GroupSection) session.get(GroupSection.class,e.getMsectionid());
Query query1 = session.createQuery("from GroupNotes as gn where gn.ownednotes.msectionid=:msectionid and gn.noteCreationTime >:limit");
query1.setParameter("limit", nowMinusDaysAsTimestamp);
query1.setParameter("msectionid",e.getMsectionid());
notesList.addAll(query1.list());
}
// I am getting the data below, but I get JSON errors.
for(GroupNotes groupNotes : notesList){
System.out.println("Group notes found are "+groupNotes.getMnotetext());
}
return notesList;
}
GroupCanvas model :
#Entity
#Table(name = "membercanvas")
public class GroupCanvas{
#OneToMany(mappedBy = "currentcanvas",fetch=FetchType.LAZY, cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<GroupSection> ownedsection = new HashSet<>();
#JsonIgnore
public Set<GroupSection> getOwnedsection() {
return this.ownedsection;
}
public void setOwnedsection(Set<GroupSection> ownedsection) {
this.ownedsection = ownedsection;
}
}
GroupSection model :
#Entity
#Table(name = "membersection")
public class GroupSection{
#OneToMany(mappedBy = "ownednotes", fetch = FetchType.EAGER,cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<GroupNotes> sectionsnotes = new HashSet<>();
public Set<GroupNotes> getSectionsnotes(){
return this.sectionsnotes;
}
public void setSectionsnotes(Set<GroupNotes> sectionsnotes){
this.sectionsnotes=sectionsnotes;
}
}
GroupNotes model :
#Entity
#Table(name="groupnotes")
public class GroupNotes{
#Id
#Column(name="mnoteid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "mnote_gen")
#SequenceGenerator(name = "mnote_gen",sequenceName = "mnote_seq")
#org.hibernate.annotations.Index(name = "mnoticesidindex")
private int mnoticesid;
#Column(name = "mnotetext")
private String mnotetext;
#Column(name = "mnoteheadline")
private String mnotetag;
#Column(name = "mnotecolor")
private String mnotecolor;
#Column(name = "mnoteorder")
private double mnoteorder;
#Column(name = "attachmentcount")
private int attachCount;
#Column(name = "notedate")
private String noteDate;
#Column(name = "uploader")
private String uploader;
#Column(name = "activeedit")
private boolean activeEdit;
#Column(name = "notedisabled")
private boolean noteDisabled;
#Column(name = "noteinactive")
private boolean noteInActive;
#Column(name = "notecreatoremail")
private String noteCreatorEmail;
#Column(name = "prefix")
private String prefix;
#Column(name = "timestamp")
private Timestamp noteCreationTime;
#Transient
private boolean notRead;
#Transient
private String tempNote;
#Transient
private String canvasUrl;
#ManyToOne
#JoinColumn(name = "msectionid")
#JsonIgnore
private GroupSection ownednotes;
#JsonIgnore
public GroupSection getOwnednotes(){return this.ownednotes;}
public void setOwnednotes(GroupSection ownednotes){this.ownednotes=ownednotes;}
#JsonIgnore
public int getOwnedSectionId(){
return this.ownednotes.getMsectionid();
}
#OneToMany(mappedBy = "mnotedata",fetch = FetchType.LAZY,cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<GroupAttachments> mattachments = new HashSet<>();
public Set<GroupAttachments> getMattachments() {
return this.mattachments;
}
public void setMattachments(Set<GroupAttachments> mattachments) {
this.mattachments = mattachments;
}
#OneToMany(mappedBy = "mhistory",fetch = FetchType.LAZY,cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<GroupNoteHistory> groupNoteHistorySet = new HashSet<>();
public Set<GroupNoteHistory> getGroupNoteHistorySet(){
return this.groupNoteHistorySet;
}
public void setGroupNoteHistorySet(Set<GroupNoteHistory> groupNoteHistorySet){
this.groupNoteHistorySet = groupNoteHistorySet;
}
#OneToMany(mappedBy = "unreadNotes",fetch = FetchType.LAZY,cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<UnreadNotes> unreadNotesSet = new HashSet<>();
public Set<UnreadNotes> getUnreadNotesSet(){
return this.unreadNotesSet;
}
public void setUnreadNotesSet(Set<UnreadNotes> unreadNotesSet){
this.unreadNotesSet = unreadNotesSet;
}
//getters and setters ignored
}
Error log :
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.journaldev.spring.model.GroupNotes["ownedSectionId"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.journaldev.spring.model.GroupNotes["ownedSectionId"])
Kindly let me know what to do, as I am stuck on that error since some time.

What I think that happens is Jackson tries to serialize all fields in the hierarchy based on getter methods. In some situation NullPointerException is thrown in the following method:
#JsonIgnore
public int getOwnedSectionId(){
return this.ownednotes.getMsectionid();
}
replace it with the following method:
#JsonIgnore
public int getOwnedSectionId(){
if(this.ownednotes != null)
return this.ownednotes.getMsectionid();
return 1;
}
I don't have an explanation why jackson tries to serialize it when is market with #JsonIgnore but you can give a try with my proposal

I would appreciate if anyone tells me either how to fix this error or convert the objects to a DTO objects so they can be transferred.
We use DozerMapper at work for this purpose.

Instead of doing that mapping manually you might want to take a look at Blaze-Persistence Entity Views which can be used to efficiently implement the DTO pattern with JPA. Here a quick code sample how your problem could be solved
First you define your DTO as entity view
#EntityView(GroupNotes.class)
public interface GroupNoteView {
#IdMapping("mnoticesid") int getId();
String getMnotecolor();
String getMnotetag();
String getMnotetext();
String getNoteDate();
Timestamp getNoteCreationTime();
int getAttachCount();
}
Next you adapt your DAO to make use of it
#Override
public List<GroupNoteView> searchNotesByDays(int days, int mcanvasid) {
EntityManager entityManager = // get the entity manager from somewhere
CriteriaBuilderFactory cbf = // factory for query building from Blaze-Persistence
EntityViewManager evm = // factory for applying entity views on query builders
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -days);
long daysAgo = cal.getTimeInMillis();
Timestamp nowMinusDaysAsTimestamp = new Timestamp(daysAgo);
CriteriaBuilder<GroupNotes> cb = cbf.create(entityManager, GroupNotes.class, "note")
.where("noteCreationTime").gt(nowMinusDaysAsTimestamp)
.where("ownednotes.ownedcanvas.mcanvasid").eq(mcanvasid);
return evm.applySetting(EntityViewSetting.create(GroupNoteView.class), cb)
.getResultList();
}
And finally the calling code
#RequestMapping(value = "/findgroupnotes/{days}/{canvasid}")
public #ResponseBody List<GroupNoteView> findGroupNotesByDays(#PathVariable("days")int days, #PathVariable("canvasid")int canvasid){
return this.groupNotesService.findGroupNotesByDays(days, canvasid);
}

Related

Unable to get results of controller whilst using Join Annotations in hibernate

I am new to hibernate and trying to implement join annotations of hibernate, but facing this weird issue. As I have attached pojos when I run my controller no output is seen but a new row got populated in user_song_rel table as a foreign key to the song table.
In addition, value of that entry is null(in MySQL screenshot) despite there are entries in the song table corresponding to song_ids.
Please find attached schema of both tables.
Thanks in advance :)
Schema Structure
User Song Mapping
#NamedQueries({
#NamedQuery(
name="findUserSongByUserID",
query="from UserSongRel usr where usr.user_id = :user_id"
)
})
#Entity
#Table(name="user_song_rel")
public class UserSongRel {
public UserSongRel() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int usr_id;
private String user_id;
private String song_id;
private String song_src;
private int song_state;
#Temporal(TemporalType.TIMESTAMP)
private Date add_date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "song_id")
private SongInfo songInfo;
Song
#Entity (name = "song_info")
public class SongInfo {
#Id
private String song_id;
private String s_name;
private String artist;
private String album;
private int duration;
private Date rel_date;
private int popularity;
#OneToMany(mappedBy = "songInfo")
private List<UserSongRel> userSongRelList=new ArrayList<UserSongRel>();
Controller
#RequestMapping(value = "/getUserSongs/{uid}", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<?> getUserSongs(#PathVariable String uid,
HttpServletResponse response, HttpServletRequest request){
SongInfo usr= songService.getUserSongs(uid);
return ResponseEntity.ok().body(usr);
}
DAO
public SongInfo getUserSongs(String uid) {
Session s = sf.getCurrentSession();
UserSongRel songs=s.get(UserSongRel.class, 1);
List<UserSongRel> songList =new ArrayList<UserSongRel>();
songList.add(songs);
return songList.get(0).getSongInfo();
}
Select query after running controller.

JPA Native Query Result Set Mapping to Entity class having child class

(column and variable name changed after posting question)i am writing join query using entityManager.createNativeQuery(somequery) in jpa custom method when i run code i get following error :
com.ibm.db2.jcc.am.SqlException: [jcc][10150][10300][4.12.56] Invalid >parameter: Unknown column name exc_seq_nbr. ERRORCODE=-4460, SQLSTATE=null
i am using IBM DB2 server and spring boot
exceptionTenderPK (object in entity class) is not being mapped correctly thats why getting invalid column can someone please tell me how to map exceptionTenderPK object class
Note: i cant use #OneToMany in this case because tables are unrelated
#Entity
#Table(name = "Table_name")
#Data
public class MainPojoclass {
#EmbeddedId
#JsonProperty(value = "mainPojoclassPK")
private MainPojoclassPK mainPojoclassPK;
#Column(name = "amt")
#JsonProperty(value = "amt")
private BigDecimal amt;
#Column(name = "tndid")
#JsonProperty(value = "tndid")
private String tndid;
#Column(name = "cde")
#JsonProperty(value = "cde")
private String cde;
#Column(name = "ind")
#JsonProperty(value = "ind")
private String ind;
#Column(name = "user")
#JsonProperty(value = "user")
private String user;
#Column(name = "updatedtime")
#JsonProperty(value = "updatedtime")
private Date updatedtime;
#Column(name = "src")
#JsonProperty(value = "src")
private String src;
#Column(name = "stat")
#JsonProperty(value = "stat")
private String stat;
}
#Transactional
public interface JoinQueryRepository extends JpaRepository<MainPojoclass, Long>, JoinQueryRepositoryCustom{
}
public interface JoinQueryRepositoryCustom {
List<MainPojoclass> getGRDetails(MainPojoclass et,Date reportDate);
}
public class JoinQueryRepositoryImpl implements JoinQueryRepositoryCustom {
#PersistenceContext
EntityManager entityManager;
#SuppressWarnings("all")
#Override
public List<MainPojoclass> getGRDetails(MainPojoclass et,Date rdate) {
String queryStr = "select et.Salss_DTE from table et"
+ " join dte etr on et.Salss_DTE = etr.Salss_DTE where et.nbr =? ";
List<MainPojoclass> datalist = null;
Query query = entityManager.
createNativeQuery(queryStr,"mapping")
.setParameter(1, 222);
datalist = query.getResultList();
return datalist;
}
}
The error says that there is no column exc_seq_nbr and you used that in your EntityResult mapping.
In your query you only return et.SLS_DTE you have to return all columns that are in the result set mapping.
Hi all since i am not getting any solutions i am going with below solution it works for me and removing #SqlResultSetMapping below code is working without sql result set mapping
Query q = em.createNativeQuery(queryStr);
List<Object[]> resultList = q.getResultList();
for (Object[] result : resultList) {
entityObj.setReason(result[0].toString);
//rest attribute will convert from result[1].toString to corresponding
// data type and set to entity object
}

Jackson ObjectMapper Hibernate issue

I am working on a Spring Boot 2.0 / Java 8 shopping cart online application. I use Hibernate as the ORM framework.
I have two entities, Order and OrderDetail shown below:
#Entity
#Table(name = "orders")
public class Order extends AbstractEntityUuid {
#Column(name = "order_number", unique = true)
private String orderNumber;
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#Column(name = "total_amount")
private BigDecimal totalAmount = BigDecimal.ZERO;
#CreatedDate
#Column(name = "created_on", columnDefinition = "DATETIME", updatable = false)
protected LocalDateTime created;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JsonManagedReference
private Set<OrderDetail> items = new HashSet<>();
#OneToOne(fetch = FetchType.LAZY, orphanRemoval = true)
#JoinColumn(nullable = false, name = "card_details_id")
private CardDetails card;
#OneToOne(fetch = FetchType.LAZY, orphanRemoval = true)
#JoinColumn(nullable = false, name = "shipping_address_id")
private Address shippingAddress;
#OneToOne(fetch = FetchType.LAZY, orphanRemoval = true)
#JoinColumn(name = "billing_address_id")
private Address billingAddress;
//getters and setters
}
#Entity
#Table(name = "order_detail")
public class OrderDetail extends AbstractPersistable<Long> {
#Column(name = "quantity")
private Integer quantity;
#Column(name = "total_amount")
private BigDecimal totalAmount = BigDecimal.ZERO;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id", nullable = false)
#JsonBackReference
private Order order;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "product_id", nullable = false)
private Product product;
//getters and setters
}
When the user heads over to his Orders, he should be able to see information related only to the order itself (no details).
For that reason, I retrieve data only from the order table. Following is my repository:
public interface OrderRepository extends CrudRepository<Order, Long> {
#Query("FROM Order o WHERE o.user.email = ?1")
List<Order> findOrdersByUser(String email);
}
In the service, what I do is simply calling the above method and converting to a the dto counterpart.
#Transactional(readOnly = true)
public List<OrdersPreviewDTO> getOrdersPreview(String email) {
List<Order> orders = orderRepository.findOrdersByUser(email);
return orderConverter.convertToOrderPreviewDTOs(orders);
}
The converter uses an Jackson ObjectMapper object under the hood.
List<OrdersPreviewDTO> convertToOrderPreviewDTOs(List<Order> orders) {
return orders.stream()
.map(o -> objectMapper.convertValue(o, OrdersPreviewDTO.class))
.collect(toList());
}
The objectMapper is inject by Spring and defined in a configuration class:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
return objectMapper;
}
The OrdersPreviewDTO dto object contains just a subset of the Order entity, because as I already mentioned, in the Orders page I want to show only high level properties of the user's orders, and nothing related to their details.
#JsonIgnoreProperties(ignoreUnknown = true)
public class OrdersPreviewDTO {
private String orderNumber;
#JsonFormat(pattern = "dd/MM/yyyy HH:mm")
private LocalDateTime created;
private BigDecimal totalAmount;
#JsonCreator
public OrdersPreviewDTO(
#JsonProperty("orderNumber") String orderNumber,
#JsonProperty("created") LocalDateTime created,
#JsonProperty("totalAmount") BigDecimal totalAmount) {
this.orderNumber = orderNumber;
this.created = created;
this.totalAmount = totalAmount;
}
//getters and setters
}
Everything works fine, the order entity is converted automagically by Jackson into its dto counterpart.
The problem come out when looking at the query executed by Hibernate under the hood.
Hibernate unwrap the collection of order details for each order and execute a query to retrieve data from each child collection:
select carddetail0_.id as id1_2_0_, carddetail0_.brand as brand2_2_0_, carddetail0_.created as created3_2_0_, carddetail0_.exp_month as exp_mont4_2_0_, carddetail0_.exp_year as exp_year5_2_0_, carddetail0_.last4 as last6_2_0_ from card_details carddetail0_ where carddetail0_.id=?
select address0_.id as id1_1_0_, address0_.created as created2_1_0_, address0_.last_modified as last_mod3_1_0_, address0_.city as city4_1_0_, address0_.country as country5_1_0_, address0_.first_name as first_na6_1_0_, address0_.last_name as last_nam7_1_0_, address0_.postal_code as postal_c8_1_0_, address0_.state as state9_1_0_, address0_.street_address as street_10_1_0_, address0_.telephone as telepho11_1_0_ from address address0_ where address0_.id=?
select items0_.order_id as order_id4_4_0_, items0_.id as id1_4_0_, items0_.id as id1_4_1_, items0_.order_id as order_id4_4_1_, items0_.product_id as product_5_4_1_, items0_.quantity as quantity2_4_1_, items0_.total_amount as total_am3_4_1_ from order_detail items0_ where items0_.order_id=?
and tons of others more.
Whether I modify the code in the following way, Hibernate runs only the expected query on the Order table:
This line of code:
objectMapper.convertValue(o, OrdersPreviewDTO.class)
is replaced by the following dirty fix:
new OrdersPreviewDTO(o.getOrderNumber(), o.getCreated(), o.getTotalAmount())
Query run by Hibernate:
select order0_.id as id1_5_, order0_.billing_address_id as billing_6_5_, order0_.card_details_id as card_det7_5_, order0_.created_on as created_2_5_, order0_.one_address as one_addr3_5_, order0_.order_number as order_nu4_5_, order0_.shipping_address_id as shipping8_5_, order0_.total_amount as total_am5_5_, order0_.user_id as user_id9_5_
from orders order0_ cross join user user1_
where order0_.user_id=user1_.id and user1_.user_email=?
My question is. Is there a way to tell Jackson to map only the Dtos field so that it doesn't trigger lazy loading fetches through Hibernate for the non required fields?
Thank you
The short answer is no, don't try and be so clever. Manually create your DTO to control any lazy loading, then use Jackson on the DTO outside the transaction.
The Long answer is yes, you can override MappingJackson2HttpMessageConverter and control which fields get called from the entity.
#Configuration
public class MixInWebConfig extends WebMvcConfigurationSupport {
#Bean
public MappingJackson2HttpMessageConverter customJackson2HttpMessageConverter2() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(DTO1.class, FooMixIn.class);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(customJackson2HttpMessageConverter2());
}
}
Then
#Override
protected void processViews(SerializationConfig config, BeanSerializerBuilder builder) {
super.processViews(config, builder);
if (classes.contains(builder.getBeanDescription().getBeanClass())) {
List<BeanPropertyWriter> originalWriters = builder.getProperties();
List<BeanPropertyWriter> writers = new ArrayList<BeanPropertyWriter>();
for (BeanPropertyWriter writer : originalWriters) {
String propName = writer.getName();
if (!fieldsToIgnore.contains(propName)) {
writers.add(writer);
}
}
builder.setProperties(writers);
}
}
}
here is a working example.
+1 for Essex Boy answer. I just want to add that you can directly return DTO from your JPQL Queries instead of using Jackson. It avoids a transformation from the database to your object Order and then another transformation from Order object to OrdersPreviewDTO object.
For example, you need to change your query in your repository to do it. It would be something like :
public interface OrderRepository extends CrudRepository<Order, Long> {
#Query("SELECT new OrdersPreviewDTO(o.order_number, o.created_on, o.total_amount)) FROM Order o WHERE o.user.email = ?1")
List<OrdersPreviewDTO> findOrdersByUser(String email);
}
If OrdersPreviewDTO is strictly a subset of your Order class, why not simply use the #JsonView annotation to automatically create a simple view in your controller? See https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring for example.
In case you need a DTO for both input and output, also consider using http://mapstruct.org/

Test on #Transactional method fails:Concurrent update in table "WORK": another transaction has updated or deleted the same row

I have two entities and a service. Without #Transactional everything worked fine (except rollback). Now I added a #Transactional to the service method to make it an transaction and rollback automatically on errors. But now all tests using this method fail with javax.persistence.EntityNotFoundException: Unable to find org.kitodo.mediaserver.core.db.entities.Work with id xyz (xyz is the ID of my Work item).
Then I tried to add cascade = {CascadeType.PERSIST, CascadeType.MERGE} to the work field of ActionData entity. Than I get another exception on the same position as before: org.h2.jdbc.JdbcSQLException: Concurrent update in table "WORK": another transaction has updated or deleted the same row [90131-196]
I assume for some reason it tries to use two transitions at the same time.
What's the reason and how can I make this work?
The entities
#Entity
public class Work {
private String id;
private String title;
private String path;
private String hostId;
private Instant indexTime;
private Set<Collection> collections;
private String allowedNetwork = "global";
protected Work() {}
public Work(String id, String title) {
this.id = id;
this.title = title;
}
#Id
public String getId() {
return id;
}
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "work_collection",
joinColumns = #JoinColumn(name = "work_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "collection_name", referencedColumnName = "name"))
public Set<Collection> getCollections() {
return collections;
}
// getters/setters
}
#Entity
public class ActionData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ElementCollection
#CollectionTable(name = "action_parameter")
private Map<String, String> parameter = new HashMap<>();
#ManyToOne
#JoinColumn(name = "work_id", referencedColumnName = "id", nullable = false)
private Work work;
private String actionName;
private Instant requestTime;
private Instant startTime;
private Instant endTime;
private ActionData() {}
public ActionData(Work work, String actionName, Map<String, String> parameter) {
this.work = work;
this.parameter = parameter;
this.actionName = actionName;
}
// getters/setters
}
The Service method
#Service
public class ActionService {
#Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public Object performRequested(ActionData actionData) throws Exception {
// some checks
actionData.setStartTime(Instant.now());
// !!! javax.persistence.EntityNotFoundException: Unable to find org.kitodo.mediaserver.core.db.entities.Work with id xyz
actionRepository.save(actionData);
IAction actionInstance = getActionInstance(actionData.getActionName());
Object result;
result = actionInstance.perform(actionData.getWork(), actionData.getParameter());
actionData.setEndTime(Instant.now());
actionRepository.save(actionData);
return result;
}
}
The test
#Test
public void performRequestedAction() throws Exception {
// given
init();
work1 = entityManager.persist(work1);
actionData1 = new ActionData(work1, "mockAction", parameter1);
actionData1.setRequestTime(Instant.now());
actionData1 = entityManager.persist(actionData1);
entityManager.flush();
// when
Object action = actionService.performRequested(actionData1);
// then
assertThat(action).isNotNull();
assertThat(action).isInstanceOf(String.class);
assertThat(action).isEqualTo("performed");
assertThat(actionData1.getStartTime()).isBetween(Instant.now().minusSeconds(2), Instant.now());
assertThat(actionData1.getEndTime()).isBetween(Instant.now().minusSeconds(2), Instant.now());
}
I suspect, your Unittest is not working in autocommit mode.
The problem might be, that you don't commit the inserting-transaction in your testing function.
Therefore the saved data can not be seen by the called method actionService.performRequested() which starts a completely new transaction. This transaction will not be allowed to see any dirty data.
So either make sure that the data is saved either by setting autocommit-mode or committing the transaction which persists actionData1 in performRequestedAction.

hibernate filter not working in the case of session.get() but working in the case of session.createQuery()

I am using hibernate 4. I am writing a filter. The strange thing I noticed is the filter is not getting applied if I use session.get() method
public SecurityAgency getSecurityAgencyById(int id) {
Session session = this.sessionFactory.getCurrentSession();
session.enableFilter("byEnabled");
SecurityAgency s = (SecurityAgency)session.get(SecurityAgency.class, new Integer(id));
return s;
}
Filter starts working as soon as I replace the session.get method with session.createQuery method and send a HQL query. I am unable to find any reason for this behaviour in the hibernate documentation.
FIlter declaration in securtiy agency class
#Entity
#Table(name="security_agency")
public class SecurityAgency implements java.io.Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name="name")
private String name;
#Column(name="code")
private String code;
#Column(name="website")
private String website;
#Column(name="tan")
private String tan;
#Column(name="email")
private String email;
#Column(name="pan")
private String pan;
#Column(name="created_at")
private Date createdAt;
#Column(name="created_by")
private long createdBy;
#Column(name="modified_at")
private Date modifiedAt;
#Column(name="modified_by")
private long modifiedBy;
#OneToMany(mappedBy="securityAgency",fetch = FetchType.EAGER)
#JsonIgnoreProperties("securityAgency")
#Filter(name = "byEnabled", condition = "is_enabled= 1")
private Set<ContactPerson> contactPersons = new HashSet<ContactPerson>(0);
public SecurityAgency() {
}
Contact person class
#Entity
#Table(name = "contact_person")
#FilterDefs({
#FilterDef(name="byEnabled"),
#FilterDef(name="bySecurityAgency",parameters = #ParamDef(name="agency_id", type="int"))
})
#Filters({
#Filter(name="byEnabled", condition = "is_enabled = 1"),
#Filter(name="bySecurityAgency", condition = "agency_id= :agency_id ")
})
public class ContactPerson implements java.io.Serializable {
Filter doesn't work if you are fetching using id value.Use Query interface instead. See this thread
if you want to use table column values you need to use filter join table ( #FilterJoinTable ), #Filter is applied to target entity rather than table
try,
#FilterJoinTable(name = "byEnabled", condition = "is_enabled= :enabled")
private Set<ContactPerson> contactPersons = new HashSet<ContactPerson>(0);
get
session.enableFilter("byEnabled").setParameter("enabled", Integer.valueOf(1));

Categories