I have a room service which returns detail for rooms when requesting http://localhost:8082/room/search/byRoomChar
#GetMapping(path = "/search/byRoomChar")
public #ResponseBody List<Room> byCapacity(#RequestParam(required = false) Integer capacity,
#RequestParam(required = false) Boolean isUnderMaintenance,
#RequestParam(required = false) String equipment) {
return roomRepository.findByRoomChar(capacity, isUnderMaintenance, equipment);
}
Now I want to request this #GetMapping from the booking service since this is the application gateway that users are going to interact with using the http://localhost:8081/booking/search/byRoomChar.
#GetMapping(path = "/search/byRoomChar")
public #ResponseBody List<Room> byCapacity(#RequestParam(required = false) Integer capacity,
#RequestParam(required = false) Boolean isUnderMaintenance,
#RequestParam(required = false) String equipment) {
ResponseEntity<Room[]> roomsResponse = restTemplate.getForEntity("http://localhost:8082/room/search/byRoomChar?capacity=" + capacity + "&isUnderMaintenance=" +
isUnderMaintenance + "&equipment=" + equipment, Room[].class);
return Arrays.asList(roomsResponse.getBody());
}
Room entity code:
package nl.tudelft.sem.template.entities;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "Room")
public class Room {
#EmbeddedId
private RoomId id;
#Column(name = "capacity")
private int capacity;
#Column(name = "numberOfPeople")
private int numberOfPeople;
#Column(name = "isUnderMaintenance", nullable = false)
private boolean isUnderMaintenance;
#Column(name = "equipment")
private String equipment;
public Room() {
}
public Room(long roomNumber, long buildingNumber, int capacity,
int numberOfPeople, boolean isUnderMaintenance, String equipment) {
RoomId id = new RoomId(roomNumber, buildingNumber);
this.id = id;
this.capacity = capacity;
this.numberOfPeople = numberOfPeople;
this.isUnderMaintenance = isUnderMaintenance;
this.equipment = equipment;
}
public RoomId getId() {
return id;
}
public void setId(RoomId id) {
this.id = id;
}
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public int getNumberOfPeople() {
return numberOfPeople;
}
public void setNumberOfPeople(int numberOfPeople) {
this.numberOfPeople = numberOfPeople;
}
public boolean getIsUnderMaintenance() {
return isUnderMaintenance;
}
public void setUnderMaintenance(boolean underMaintenance) {
isUnderMaintenance = underMaintenance;
}
public String getEquipment() {
return equipment;
}
public void setEquipment(String equipment) {
this.equipment = equipment;
}
}
Room repository code:
package nl.tudelft.sem.template.repositories;
import java.util.List;
import nl.tudelft.sem.template.entities.Room;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
#Repository
public interface RoomRepository extends JpaRepository<Room, Integer> {
#Query("SELECT r FROM Room r WHERE (:number is null or r.id.number = :number)"
+ "and r.id.buildingNumber = :buildingNumber")
List<Room> findByRoomNum(#Param("number") Long number,
#Param("buildingNumber") Long buildingNumber);
#Query("SELECT r FROM Room r WHERE (:capacity is null or r.capacity = :capacity) and"
+ "(:isUnderMaintenance is null or r.isUnderMaintenance = :isUnderMaintenance) and"
+ "(:equipment is null or r.equipment = :equipment)")
List<Room> findByRoomChar(#Param("capacity") Integer capacity,
#Param("isUnderMaintenance") Boolean isUnderMaintenance,
#Param("equipment") String equipment);
}
However, this does not work because when omitting parameters when calling the getmapping from the booking service all parameter values are turned into null because of the required=false. And this are converted into Strings inside the hard coded url.
2021-12-04 17:13:03.883 WARN 16920 --- [nio-8082-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Boolean'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value [null]]
How can I make a get http request with optional parameters from within the code?
UriComponentsBuilder can help with URI construction. It correctly handles nullable query parameters.
String uri = UriComponentsBuilder.fromHttpUrl("http://localhost:8082/room/search/byRoomChar")
.queryParam("capacity", capacity)
.queryParam("isUnderMaintenance", isUnderMaintenance)
.queryParam("equipment", equipment)
.encode().toUriString();
ResponseEntity<Room[]> roomsResponse = restTemplate.getForEntity(uri, Room[].class);
Also, following answer can be helpful: https://stackoverflow.com/a/25434451/5990117
If the parameters are not mandatory in your Room API but you still use them in your call to the Database either you have sensible defaults if they are actually not provided by the user. Something along the following lines (in this case you actually don't need to explicitly define required = false):
#GetMapping(path = "/search/byRoomChar")
public #ResponseBody List<Room> byCapacity(#RequestParam(defaultValue = "10") Integer capacity,
#RequestParam(defaultValue = "false") Boolean isUnderMaintenance,
#RequestParam(defaultValue = "default-equipment") String equipment) {
return roomRepository.findByRoomChar(capacity, isUnderMaintenance, equipment);
}
Or you define a Repository method with no additional parameters, but this might be trickier since you basically need all the possibilities of null and non-null parameters.
This is because the URI string being built when the parameters are null is like the follwing:
http://localhost:8082/room/search/byRoomChar?isUnderMaintenance=null
Since the "null" is being appended as a value of a parameter, the room server fails trying to deserialize it to a different type.
For example in the error message you gave means that the "isUnderMaintenance" should be boolean but is "null" string.
To solve this problem, I recommend using the UriComponentBuilder.
#Test
fun constructUriWithQueryParameter() {
val uri = UriComponentsBuilder.newInstance()
.scheme("http")
.host("localhost")
.port(8082)
.path("/room/search/byRoomChar")
.query("capacity={capa}")
.query("isUnderMaintenance={isUnderMaintenance}")
.query("equipment={equip}")
.buildAndExpand(null, null, null)
.toUriString()
assertEquals(
"http://localhost:8082/room/search/byRoomChar
capacity=&isUnderMaintenance=&equipment=",
uri
)
}
I tried Petr Aleksandrov answer. Looks very clean and it is most probably the way to go, but I was getting an "not absolute uri" exception.
Didn't have time to look for answers so I created my workaround code. Messy but it worked.
#GetMapping(path = "/search/byRoomChar")
public #ResponseBody List<Room> byCapacity(#RequestParam(required = false) Integer capacity,
#RequestParam(required = false) Boolean isUnderMaintenance,
#RequestParam(required = false) String equipment) {
if(capacity == null && isUnderMaintenance == null && equipment == null) {
ResponseEntity<Room[]> roomsResponse = restTemplate.getForEntity("http://localhost:8082/room/search/byRoomChar", Room[].class);
return Arrays.asList(roomsResponse.getBody());
}
String url = "http://localhost:8082/room/search/byRoomChar?";
if(capacity != null) {
url += "capacity=" + capacity + "&";
}
if(isUnderMaintenance != null) {
url += "isUnderMaintenance=" + isUnderMaintenance + "";
}
if(equipment != null) {
url += "equipment=" + equipment;
}
ResponseEntity<Room[]> roomsResponse = restTemplate.getForEntity(url, Room[].class);
return Arrays.asList(roomsResponse.getBody());
}
Related
I have an entity where I set the max for every String field like the following:
#Column(name = "abc")
#Size(max = 10)
private String abc;
#Column(name = "xyz")
#Size(max = 50)
private String xyz;
I want to write a Converter to truncate that field if exceeds max size. Something like this:
import javax.persistence.AttributeConverter;
import javax.persistence.Convert;
#Convert
public class TruncatedStringConverter implements AttributeConverter<String, String> {
private static final int LIMIT = 999;
#Override
public String convertToDatabaseColumn(String attribute) {
if (attribute == null) {
return null;
} else if (attribute.length() > LIMIT) {
return attribute.substring(0, LIMIT);
} else {
return attribute;
}
}
#Override
public String convertToEntityAttribute(String dbData) {
return dbData;
}
}
But the thing is that I can't use LIMIT since I need to look to the Size annotation. Is there any way to access the field name from the converter?
This way I could use reflection to read the max size.
Thank you!
I am currently developing a data warehouse with spring boot, hibernate and querydsl.
Nearly everything is working fine, but I got trouble doing a search request for one of my entity called group. The errors, are not really helpful:
My request is simple /group/advancedSearch?page=0&size=10&sort=name,asc&search=groupCode:dfa,name:dfa,
The errors raise in my service when I do call the repository method.
antlr.NoViableAltException: unexpected token: group
[...]
java.lang.NullPointerException: null
To make this more understandable my code is below. I have the same method for most of my entities and there it is working fine. Because I had no clue where the unexpected token group might come from, I had a look at the generated class QGroup, there I found this peace of code public static final QGroup group = new QGroup("group1");. The name group1 made me wonder, but I'm not sure if it has anything to do with the errors. In all other classes the string was always the name of the class with initial letters small.
I thought the entity group might be duplicated, so querydsl would create group and group1, but that's not the case. So any ideas where the errors might come from and how to prevent / fix them?
The entity:
#Entity
#Table(name = "[Group]")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Group_ID")
private long groupId;
#ManyToOne()
#JoinColumn(name = "Dimension_ID")
private Dimension dimension;
#Column(name = "Dimension_ID", updatable = false, insertable = false)
private Long dimensionId;
#Column(name = "GroupCode")
private String groupCode;
#Column(name = "Name")
private String name;
[...]
}
The function of the controller where the errors raise:
#RequestMapping(value = GROUP_URL + "/advancedSearch", method = RequestMethod.GET)
#ResponseBody
public PagedResources<Group> advancedSearch(
#RequestParam(value = "search", required = false) String search,
Pageable pageable, #RequestParam MultiValueMap<String, String> parameters,
PersistentEntityResourceAssembler persistentEntityResourceAssembler
) {
SimpleGrantedAuthority[] allowedRoles = {SYSADMIN};
GeneralPredicateBuilder<Group> builder = new GeneralPredicateBuilder<>(Group.class);
Predicate predicate = predicateService.getPredicateFromParameters(parameters, Group.class);
Page<Group> results = service.advancedSearch(
this.buildAdvancedSearch(search, predicate, builder), pageable, allowedRoles);
return super.toPagedResource(results, persistentEntityResourceAssembler);
}
public Predicate buildAdvancedSearch(String search, Predicate predicate, GeneralPredicateBuilder<T> builder) {
if (search != null) {
Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
}
BooleanExpression expression = builder.build();
if (predicate != null) {
predicate = expression.and(predicate);
} else {
predicate = expression;
}
}
return predicate;
}
The PredicateService:
#Service
public class PredicateService {
#Autowired
private final QuerydslPredicateBuilder querydslPredicateBuilder;
#Autowired
private final QuerydslBindingsFactory querydslBindingsFactory;
public PredicateService(QuerydslPredicateBuilder querydslPredicateBuilder, QuerydslBindingsFactory querydslBindingsFactory) {
this.querydslPredicateBuilder = querydslPredicateBuilder;
this.querydslBindingsFactory = querydslBindingsFactory;
}
public <T> Predicate getPredicateFromParameters(final MultiValueMap<String, String> parameters, Class<T> tClass) {
TypeInformation<T> typeInformation = ClassTypeInformation.from(tClass);
return querydslPredicateBuilder.getPredicate(typeInformation, parameters, querydslBindingsFactory.createBindingsFor(typeInformation));
}
}
The service method:
public Page<Group> advancedSearch(Predicate predicate, Pageable pageable, SimpleGrantedAuthority[] roles){
if (SecurityUtils.userHasAnyRole(roles)) {
return this.repository.findAll(predicate, pageable); // <-- here the errors raise
} else throw new ForbiddenException(FORBIDDEN);
}
The repository:
#RepositoryRestResource(collectionResourceRel = GROUP_URL, path = GROUP_URL)
#CrossOrigin(exposedHeaders = "Access-Control-Allow-Origin")
public interface GroupRepository extends PagingAndSortingRepository<Group, Long>, JpaSpecificationExecutor<Group>, QuerydslPredicateExecutor<Group> {
}
The generated class QGroup by querydsl:
#Generated("com.querydsl.codegen.EntitySerializer")
public class QGroup extends EntityPathBase<Group> {
private static final long serialVersionUID = 384278695L;
private static final PathInits INITS = PathInits.DIRECT2;
public static final QGroup group = new QGroup("group1"); // <-- this is confusing
[...]
Update:
I finally found the generated query:
select group1
from Group group1
where ?1 = ?1 and lower(group.groupCode) like ?2 escape '!'
I think here is the problem. Form a SQL developer view, group.groupCode should be group1.groupCode. Anyone knows how to fix this?
Update 2 [2020-02-14]:
The GeneralPredicateBuilder:
public class GeneralPredicateBuilder<T> {
private List<SearchCriteria> params;
private final Class<T> type;
public GeneralPredicateBuilder(Class<T> type) {
this.params = new ArrayList<>();
this.type = type;
}
public GeneralPredicateBuilder<T> with(String key, String operation, Object value) {
params.add(new SearchCriteria(key, operation, value));
return this;
}
public BooleanExpression build() {
if (params.size() == 0) {
return null;
}
List<BooleanExpression> predicates = params.stream().map(param -> {
GeneralPredicate<T> predicate = new GeneralPredicate<T>(param, type);
BooleanExpression tmp = predicate.getPredicate();
return tmp;
}).filter(Objects::nonNull).collect(Collectors.toList());
BooleanExpression result = Expressions.asBoolean(true).isTrue();
for (BooleanExpression predicate : predicates) {
result = result.and(predicate);
}
return result;
}
public List<Predicate> buildPredicate(){
if (params.size() == 0) {
return null;
}
return params.stream().map(param -> {
GeneralPredicate<T> predicate = new GeneralPredicate<>(param, type);
return predicate.getPredicate();
}).filter(Objects::isNull).collect(Collectors.toList());
}
}
I still don't understand why the, by querydsl, generated classname of Group is group1, but in combination with my GenericPredicateBuilder and the GenericPredicate this leads to the inconsistent sql, as shown in the question. But I was finally able to fix this, unfortunately in a really dirty way. For completeness here is my GeneralPredicate:
public class GeneralPredicate<T> {
private SearchCriteria searchCriteria;
private final Class<T> type;
private final String variable;
public GeneralPredicate(SearchCriteria param, Class<T> type) {
searchCriteria = param;
this.type = type;
if(type.getSimpleName().equals("Group")){
this.variable = "group1";
} else {
this.variable = type.getSimpleName().replaceFirst("" + type.getSimpleName().charAt(0), "" + type.getSimpleName().charAt(0)).toLowerCase();
}
}
public BooleanExpression getPredicate() {
PathBuilder<T> entityPath = new PathBuilder<T>(type, variable);
if (isNumeric(searchCriteria.getValue().toString())) {
NumberPath<Integer> path = entityPath.getNumber(searchCriteria.getKey(), Integer.class);
int value = Integer.parseInt(searchCriteria.getValue().toString());
switch (searchCriteria.getOperation()) {
case ":":
return path.eq(value);
case ">":
return path.goe(value);
case "<":
return path.loe(value);
}
} else {
StringPath path = entityPath.getString(searchCriteria.getKey());
switch (searchCriteria.getOperation()) {
case ":":
return path.containsIgnoreCase(searchCriteria.getValue().toString());
case "<":
return path.startsWith(searchCriteria.getValue().toString());
case ">":
return path.endsWith(searchCriteria.getValue().toString());
}
}
return null;
}
}
You find the dirty fix within the constructor. I really hate it, but it is working and the generated sql is okay.
Maybe I use the generic in a wrong way. I am open for advices.
I'm working on a Spring Boot 2.0.5.RELEASE project.
I have a field in an Oracle database declared as CHAR(1) with a JPA converter as follows:
public class CharToBooleanConverter implements AttributeConverter<String, Boolean> {
#Override
public Boolean convertToDatabaseColumn(String s) {
return s.equalsIgnoreCase("t");
}
#Override
public String convertToEntityAttribute(Boolean aBoolean) {
if(aBoolean.equals(true)){
return "t";
} else {
return "f";
}
}
}
This converter is used in my StructureElement class twice:
#Entity
#Table(name = "OBS_STRUCTURE_ELEMENT2")
#SequenceGenerator(name = "structure_element_seq", sequenceName = "structure_element_seq", allocationSize = 1)
public class StructureElement {
#Id
#Column(name = "NO_ELEMENT")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "structure_element_seq")
private long id;
#Column(name = "TAG")
private String tag;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "SUITE")
private int sequence;
#Column(name = "OPTIONNEL")
#Convert(converter = CharToBooleanConverter.class)
private boolean optional;
#Column(name = "REPETITIF")
#Convert(converter = CharToBooleanConverter.class)
private boolean repetitive;
#ManyToOne
#JoinColumn(name = "NOM_STRUCTURE_TYPE")
private Structure typeStructure;
#Embedded
private PersistenceSignature signature;
}
The problem is that when I try to send a Structure through a RestController I receive the following in console:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Could not set field value [t] value by reflection : [class be.solodoukhin.domain.StructureElement.optional] setter of be.solodoukhin.domain.StructureElement.optional; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Could not set field value [t] value by reflection : [class be.solodoukhin.domain.StructureElement.optional] setter of be.solodoukhin.domain.StructureElement.optional (through reference chain: be.solodoukhin.domain.Structure["elements"])]
With an 500 Internal Server Error response.
Here is my method:
#RestController
#RequestMapping("/structure")
public class StructuresController {
#GetMapping("/{name}")
public ResponseEntity<Structure> getOne(#PathVariable("name") String name)
{
LOGGER.info("Call to StructuresController.getOne with name = " + name);
Optional<Structure> found = this.structureRepository.findById(name);
if(found.isPresent()){
return ResponseEntity.ok(found.get());
}
else
{
return ResponseEntity.badRequest().body(null);
}
}
}
I've tried to use hibernate specific annotation #Type(type = "true_false"). It works but this produces an uppercase T or F in the database.
I've tried to write a JSON serializer that extends com.fasterxml.jackson.databind.ser.std.StdSerializer using this link without success.
Isn't your converter the wrong way round? Your convertToDatabaseColumn has it going from a String to a Boolean. Surely you want it going from Boolean to String. And the convertToEntityAttribute going from String to Boolean.
I suspect that the convert is returning a value of "t" or "f" which can't then be put into the boolean field in the entity.
I think it should be...
public class CharToBooleanConverter implements AttributeConverter<Boolean, String> {
#Override
public Boolean convertToEntityAttribute(String s) {
return s != null && s.equalsIgnoreCase("t");
}
#Override
public String convertToDatabaseColumn(Boolean aBoolean) {
return (aBoolean != null && aBoolean) ? "t" : "f";
}
}
I am pretty new to java development but I have developed couple of production ready applications on PHP and Python. I am developing a REST api using spring boot framework and find it little confusion in terms to handling/parsing the request body. In the other languages I have worked on, it was much simpler.
If its in python/php, I need not define all the parameters of the request explicitly to handle the request body. But in java, I have to predefine all the request parameters in a POJO class and MAP it. So for every API endpoint I make, I will have to define all in a Java class as the data layer.
But in other languages, I dont need to map anything to an array, in php $_POST holds the data objects.
My question is
I have the following requests
1.
{
"category": "product/invoice/event",
"item_id": "Unique tool identifier id",
"platforms_id": "1",
"share_platform_settings": {
"fb_share_type": "page/profile",
"fb_share_name": "profilename/pagename",
"fb_id": "fb_uid/page_id"
}
}
2.
{
"category": "product/invoice/event",
"item_id": "Unique tool identifier id",
"platforms_id": "1",
"share_platform_settings": {
"twitter_username": "page/profile",
"twitter_user_access_token": "profilename/pagename"
}
}
I had written a class
import com.google.common.base.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
#Entity
public class User {
#Id
#NotNull
#Size(max = 64)
#Column(name = "id", nullable = false, updatable = false)
private String id;
private String category;
private String item_id;
private String platforms_id;
private String fb_share_type;
private String fb_share_name;
private String fb_id;
User() {
}
public User(final String id, final String category,final String item_id,final String platforms_id,final String fb_share_type,final String fb_share_name,final String fb_id) {
this.id = id;
this.category = category;
this.item_id = item_id;
this.platforms_id = platforms_id;
this.fb_share_type = fb_share_type;
this.fb_share_name = fb_share_name;
this.fb_id = fb_id;
}
public String getId() {
return id;
}
public String getCategory() {
return category;
}
public String getItemId() {
return item_id;
}
public String getFbShareType() {
return platforms_id;
}
public String getFbShareName() {
return category;
}
public String getFbId() {
return category;
}
public String setCategory(String category) {
return this.category = category;
}
public void setId(String id) {
this.id = id;
}
public void setItemId(String item_id) {
this.item_id = item_id;
}
public void setFbShareType(String fb_share_type) {
this.fb_share_type = fb_share_type;
}
public void setFbShareName(String fb_share_name) {
this.fb_share_name = fb_share_name;
}
public void setFbId(String fb_id) {
this.fb_id = fb_id;
}
#Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", id)
.add("item_id", item_id)
.add("fb_share_type", fb_share_type)
.add("fb_share_name", fb_share_name)
.add("fb_id", fb_id)
.add("category", category)
.toString();
}
}
I can map the request to the class using #RequestBody Request request,
but I have define the class Request with my request params. But my request params keeps changing, I have a different json request structure 2. What would I do in that case? What if if I have n number of different requests on the same API? Do I need to create classes for each of them? or define all the variables in this class itself? Or is there anyway, I dont need anyclass, is jackson dependency used for that?
Sorry if this a dump question, I am pretty new to java development and I really appreciate understanding a question like this :P
As you are using key/value parameters in your JSON, you will need to map it with a similar structure in the Backend so you will need to use a collection of type Map like Map<String, String> share_platform_settings in your Entity.
And your Entity will be like:
#Entity
public class User {
#Id
#NotNull
#Size(max = 64)
#Column(name = "id", nullable = false, updatable = false)
private String id;
private String category;
private String item_id;
private String platforms_id;
private Map<String, String> share_platform_settings;
//Constructors, getters and setters
}
This should work for you.
I am trying to use the automatic versioning of Hibernate but when the update method f of the Session is called I do not see the version field in the where clause of the query nor is the version incremented in the database. I am doing something fundamentally wrong probably, but what? Is calling getCurrentSession of sesssionFactory an issue?
I have the following entity class:
package dslibweb.model;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
#Entity
#Table(name = "dsXCT_Recalls")
public class DsXCT_Recalls {
#Id
public String recallId;
public int version;
public String status;
//...... more properties.....
#Version
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public String getRecallId() {
return recallId;
}
public void setRecallId(String recallId) {
this.recallId = recallId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
My controller:
package recalls.controller;
#Controller
public class RecallsDataController {
#Autowired
RecallsService recallsManager;
#Autowired
AuthenticationService authService;
private static final Logger logger = Logger.getLogger(RecallsDataController.class);
private static final String SAVE_RECALLS = "MODIFY XCT RECALLS";
RecallsGrid recalls;
#RequestMapping(value = "/showRecallsGrid")
#ResponseBody
public RecallsGrid showRecallsGrid( HttpSession session, HttpServletResponse response) {
recalls = recallsManager.getRecallsDataGrid((String) session.getAttribute("socketToken"), new GridFilters(0, 0, "", "", "", "", ""));
if (recalls.getError() == null || recalls.getError().equals("")) { // no error
return recalls;
} else {
try {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, recalls.getError());
} catch (IOException e) {
e.printStackTrace();
}
return recalls;
}
}
#RequestMapping(value = "/saveRecalls" , method= RequestMethod.POST)
#ResponseBody
public String saveRecalls( HttpSession session, #RequestParam(value="ids[]", required = false) String [] ids, #RequestParam(value="statuses[]", required = false) String [] statuses){
boolean result = authService.validateUserAction((String) session.getAttribute("socketToken"), SAVE_RECALLS);
if(result)
return recallsManager.saveRecalls(ids, statuses, recalls);
else
return "You do not have authority to perform this action.";
}
}
Where I retrieve a collection of DsXCT_Recalls and show them to the user. The collection is stored in the controller. The user then changes status in one or more entities and I call the saveRecalls method of the recallManager which creates a list of only the changed entities (comparing with the collection stored in the controller).
The recallsManager (service layer) is:
package recalls.service.defaultimpl;
#Service("recallManager")
public class HibernateRecallsDataService implements RecallsService {
#Autowired
JsonRpcRequest jsonReq;
#Autowired
JsonRpcSocketWriterReader socketWriterReader;
#Autowired
JsonRpcRequestConstructor reqConstructor;
#Autowired
RecallsDao hibernateRecallsDao;
private static final Logger logger = Logger.getLogger(HibernateRecallsDataService.class);
#Transactional
public RecallsGrid getRecallsDataGrid(String socketToken, GridFilters filters) {
List<DsXCT_Recalls> recalls = hibernateRecallsDao.findRangeOfRecordsFiltered(filters);
return new RecallsGrid(recalls);
}
#Transactional()
public String saveRecalls(String[] ids, String[] statuses, RecallsGrid recalls) {
List<DsXCT_Recalls> recallList = recalls.getRecalls();
List<DsXCT_Recalls> updatedRecallList = new ArrayList<DsXCT_Recalls>();
for (int i = 0; i < ids.length; i++) {
for (DsXCT_Recalls recall : recallList) {
if (recall.recallId.equals(ids[i])) { // recall is found in the list
if (!statuses[i].equals(recall.getStatus())) { // status has changed
recall.setStatus(statuses[i]);
updatedRecallList.add(recall);
}
}
}
}
return hibernateRecallsDao.saveAll(updatedRecallList);
}
}
The saveAll method of my DAO calls one update method of hibernate session by entity changed:
package recalls.dao.hibernate;
#Repository
public class HibernateRecallsDao implements RecallsDao {
#Autowired(required = true)
#Resource(name = "mySessionFactory")
private SessionFactory sessionFactory;
#SuppressWarnings("unchecked")
public List<DsXCT_Recalls> findRangeOfRecordsFiltered(GridFilters filters) {
return sessionFactory.getCurrentSession().createQuery("from DsXCT_Recalls r WHERE SID = 0 ORDER BY Org, Bank, BIC, SetlDate").list();
}
public String saveAll(List<DsXCT_Recalls> recallList){
int count = 0;
for(DsXCT_Recalls recall:recallList){
sessionFactory.getCurrentSession().update(recall);
count++;
}
return count + " recalls were modified.";
}
}
So apparently the #Version must be above the attribute declaration and not above the getter method.. I am sure I saw this somewhere though. So much time wasted :(