Spring Boot Entity model member converter - spring

Here my problem, I have to store serialized website's cookie information in DB, and I tried to write a simple PesistentConverter to it can be convert Map to String and vice-versa. But the IDE show a warning about this:
'Basic' attribute type should not be a map
Here my Entity:
#Entity
#Table(name = "website")
public class Website implements Serializable {
#Id
#Column(name = "name", length = 16, nullable = false)
#NotNull
private String name;
#Column(name = "serialized_cookie_map", nullable = false, length = 2048)
#Convert(converter = CookieMapPersistenceConverter.class)
#NotNull
private Map<String,String> serializedCookieMap;
...
}
CookieMapPersistenceConverter:
public class CookieMapPersistenceConverter implements AttributeConverter<Map<String, String>, String> {
#Override
public String convertToDatabaseColumn(Map<String, String> stringStringMap) {
return stringStringMap.toString();
}
#Override
public Map<String, String> convertToEntityAttribute(String s) {
ObjectMapper mapper = new ObjectMapper();
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, String.class);
HashMap<String,String> convertedMap = null;
try {
convertedMap = mapper.readValue(s, mapType);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return convertedMap;
}
}
I'm using the 2.6.3 version of Spring Boot.
Many thanks your time!

According to this:
Any attributes that have no other annotations and do not reference
other entities will be automatically mapped as basic.
and according to the table from the above link map is not a basic type. so IDE is right about
'Basic' attribute type should not be a map
I suggest try the following approach to see it satisfies your requirements or not.
#Id
#Column(name = "id", length = 16, nullable = false)
#NotNull
private String id;
#ElementCollection
#MapKeyColumn(name="property")
#Column(name="value")
#CollectionTable(name="cookies", joinColumns=#JoinColumn(name="id"))
#NotNull
private Map<String,String> serializedCookieMap;
//setter and getter
and the code for repositry:
#Repository
public interface WebsiteRepository extends CrudRepository<Website, String> {}
code I did for test:
Website w = new Website();
w.setId("id");
Map<String, String> m = new HashMap<>();
m.put("key1", "value1");
m.put("key2", "value2");
m.put("key3", "value3");
w.setSerializedCookieMap(m);
repo.save(w);
which creates two table named WEBSITE and COOKIES and the content of each is like this:
WEBSITES:
ID
id
and:
COOKIES:
ID VALUE PROPERTY
id value1 key1
id value2 key2
id value3 key3
if you insist to store Serialized version of map take a look at this

I have tried same thing in my project (with same version of spring boot). I did not notice any warning.
But here are some suggestions...
The field you have annotated #convert not meant to be persisted in DB. So instead of #column try using #Transient.
You can also try using #Type( type = "json" ) instead of #convert.
Also this can be possibly issue with IDE, so just to ignore it as a
warning you can use #SuppressWarnings("JpaAttributeTypeInspection") annotation.

Related

Elasticsearch with spring boot when using index query return nullPointerExciption

I am using Elasticsearch with spring boot. A post request returns a null pointer exception, because index query is null value doesn't have index name or any things.
Look at my code
Service :
public List<Product> createProducts() {
List<Product>productList = new ArrayList<>();
for (Integer i = 0; i < 200; i++)
{
Product product = new Product();
product.setId(Long.parseLong(i.toString()));
product.setProductName(generateName());
product.setProductPrice(generatePrice());
product.setCategory(generateCategory());
if(!product.validation().equals(""))
{
throw new BadRequestAlertException(product.validation(),"Product","check input");
}
IndexQuery indexQuery=new IndexQueryBuilder().withId(i.toString()).build(); //return null
elasticsearchOperations.index(indexQuery); // here is error becouse index is null
productList.add(product);
}
return productList;
}
And this is the entity:
#JsonInclude(value = JsonInclude.Include.NON_NULL)
#Document(indexName = "product",type = "product")
public class Product implements Serializable {
private static final long serialVersionUID = 6320548148250372657L;
#Id
private Long id;
#Field(type = FieldType.Text)
private String productName;
#Field(type = FieldType.Text)
private String category;
#Field(type = FieldType.Double)
private Double productPrice;
This is the repostory:
public interface ProductSearchRepostory extends ElasticsearchRepository<Product,Long> {
List<Product> findByProductName(String name);
List<Product> findByCategory(String category);
}
You need to let the elasticsearchOperations.index method know which index to save the data in.
As per the documentation present for Spring Data Elasticsearch, in the newer versions, the index method expects an IndexCoordinates object which tells the client which index to put the data in. For the older versions ( < 4.0), this is inferred by the client based on the entity object that is being indexed.
In your code, can you please try to pass the entity while building the IndexQuery. Something like,
new IndexQueryBuilder().withId(i.toString()).withObject(product).build()

JPA Hibernate - Entity with #Loader and a function field in select, won't work properly

#Entity
#Table(name="cad_paciente")
#Loader(namedQuery = "selectInicial")
#NamedNativeQuery(
name="selectInicial",
query="select p.*, fu_obter_lista_convenios_pac(p.id) as ds_convenio from cad_paciente p where p.id = ?", resultClass = Paciente.class,
resultSetMapping = "sqlResult")
#SqlResultSetMapping(
name="sqlResult",
entities={
#EntityResult(entityClass = Paciente.class, fields={
#FieldResult(name="ds_convenio",column="ds_convenio")})})
public class Paciente {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name="id_empresa")
private Integer id_empresa;
...
#Transient
#Column(name="ds_convenio")
private String ds_convenio;
public String getDs_convenio() {
return ds_convenio;
}
public void setDs_convenio(String ds_convenio) {
this.ds_convenio = ds_convenio;
}
My Controller method "pacientes.findAll()" won't return "ds_convenio" field with the correct value, listing "null" always in my JSON return.
What do I have to do?
Try removing the annotation #Transient and provide the column as below :
#Column(name="ds_convenio")
private String ds_convenio;
#org.springframework.data.annotation.Transient specifically states to the spring framework that the Object Mapper you are using should not include this value when converting from Java Object to JSON. Also, it means that the value is not to be persisted into the database, which means you could not query over it.
Or if you want to keep it as transient itself but does not require the value to be serialized then register the object mapper as below :
#Bean
public ObjectMapper includeTransientObjectMapper() {
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(hibernate5Module);
return mapper;
}
Or in your case since you want the result of the #NamedNativeQuer in which you aliased ds_convenio, using #FieldResult might be required to get the desired result as follows :
#Entity
#Table(name="cad_paciente")
#Loader(namedQuery = "selectInicial")
#NamedNativeQuery(name="selectInicial", query="select p.*, fu_obter_lista_convenios_pac(p.id) as ds_convenio from cad_paciente p where p.id = ?", resultClass = Paciente.class)
#SqlResultSetMapping(name="Results",
entities={
#EntityResult(entityClass=com.acme.Order.class, fields={
#FieldResult(name="id", column="id"),
#FieldResult(name="id_empresa", column="id_empresa"),
........
})
public class Paciente {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name="id_empresa")
private Integer id_empresa;
...
#Transient
#Column(name="ds_convenio")
private String ds_convenio;
Read doc

MyBatis #Many / Spring-Boot

I'm beginner (sorry for my bad explanation, feel free to correct me) in MyBatis Spring-Boot, I have problem to understand and make it works #Many
I'm using 3 layer logic programming (Presentation Layer, Service Layer, Data Layer)
Thanks for your help :)
I have 3 Tables (it's TB_Products and not TB_Product as on the screenshot):
I would like to get data form table TB_Users and TB_Products to "put" it in DTO
I create 4 java object class SearchEntity, ProductEntity (for Data layer)
I create an interface SearchRepositoryMapper.
I also create a SearchService interface and SearchServiceImpl as well.
Java object class:
SearchEntity
public class SearchEntity implements Serializable{
private static final long serialVersionUID = -9143930742617602050L;
private String id;
private String firstName;
private String lastName;
private List<ProductEntity> products;
// Getters and Setters code .....
}
ProductEntity
public class ProductEntity implements Serializable{
private static final long serialVersionUID = -6525703679290992635L;
private String id;
private String productId;
private String product;
private String number;
private String date;
private String description;
// Getters and Setters code .....
}
SearchRepositoryMapper
public interface SearchRepositoryMapper {
// Get some fields from TB_Users and all fields from TB_Products
#Select("SELECT * FROM TB_Users WHERE id = #{id}")
#Results({
#Result(property = "id", column ="id"),
#Result(property = "firstName", column = "firstName"),
#Result(property = "lastName", column= "lastName"),
#Result(property = "products", javaType = List.class, column="id",
many = #Many(select = "getProductIdByUserId"))})
public SearchEntity findAllInfoByUserId(#Param("id") int id);
#Select("SELECT *, productId FROM TB_Products WHERE productId = #{id}")
public ArrayList<ProductEntity> getProductIdByUserId(#Param("id") int id);
// Find id by uderId and return null if it doesn't exist
#Select("SELECT id FROM TB_Users WHERE userId = #{userId}")
int findIdByUserId(#Param("userId") String userId);
}
SearchServiceImpl
#Service
public class SearchServiceImpl implements SearchService {
#Autowired
SearchRepositoryMapper searchRepository;
#Override
public SearchDto getAllInfoByUserId(String id) {
SearchDto returnValue = new SearchDto(); // Init returnValue as SearchDto
int searchId = searchRepository.findIdByUserId(id); // Init searchId with the TB_Users id
SearchEntity searchEntity = searchRepository.findAllInfoByUserId(searchId);
BeanUtils.copyProperties(searchEntity, returnValue);
return returnValue;
}
}
So when I execute the code and do a GET request I get this error message:
{
"message": "nested exception is org.apache.ibatis.executor.ExecutorException: Statement returned more than one row, where no more than one was expected."
}
I found out that come from the mapper and SearchEntity searchEntity = searchRepository.findAllInfoByUserId(searchId);
But i don't know how to resolve it. The way I wrote the code is wrong
Thanks to correct me
The exception clearly says that the query returns multiple results. Plese verify if the data in the table is correct.

how not to consider #NotBlank in some methods

I'm doing a restful app in Spring boot,jpa,mysql. I have annoted some of my model fields #NotBlank to print an error in the creation of an object if those fields are blank.
Now when i'm updating, I don't want to get that error if I don't set some fields in my json body.My goal is to update just the fields which are present.
So I want to know if there is a way not to consider an #NotBlank in my updating method.
This is the code source :
For the Entity
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank(name)
private String title;
#NotBlank
private String content;
//Getters and Setters
}
The controller
#RestController
#RequestMapping("/api")
public class NoteController {
#Autowired
NoteRepository noteRepository;
// Create a new Note
#PostMapping("/notes")
public Note createNote(#Valid #RequestBody Note note) {
return noteRepository.save(note);
}
// Update a Note
#PutMapping("/notes/{id}")
public Note partialUpdateNote(#PathVariable(value = "id") Long noteId,
#RequestBody Note noteDetails) {
Note note = noteRepository.findById(noteId)
.orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId));
//copyNonNullProperties(noteDetails, note);
if(note.getTitle()!= null) {
note.setTitle(noteDetails.getTitle());
}else {
note.setTitle(note.getTitle());
}
if(note.getContent()!= null) {
note.setContent(noteDetails.getContent());
}else {
note.setContent(note.getContent());
}
Note updatedNote = noteRepository.save(note);
return updatedNote;
}
// Delete a Note
#DeleteMapping("/notes/{id}")
public ResponseEntity<?> deleteNote(#PathVariable(value = "id") Long noteId) {
Note note = noteRepository.findById(noteId)
.orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId));
noteRepository.delete(note);
return ResponseEntity.ok().build();
}
}
ResourceNotFoundException is the class responsible to throws errors.
You can use groups for that.
Add two interfaces CreateGroup and UpdateGroup.
Use them by this way:
#NotBlank(groups = CreateGroup.class)
#Null(groups = UpdateGroup.class)
private String title;
In the create endpoint
#Valid #ConvertGroup(from = Default.class, to = CreateGroup.class) Note note
In the update endpoint
#Valid #ConvertGroup(from = Default.class, to = UpdateGroup.class) Note note
Probably you don't need UpdateGroup. It is just to show a common approach.
Also for the nested objects inside Note something like
#ConvertGroup(from = CreateGroup.class, to = UpdateGroup.class)
can be used.

hibernate projection NumberFormatException for input string

I am using Spring MVC + Hibernate
Generic Dao
// getAll
#SuppressWarnings("unchecked")
public <T> List<T> getAll(Class<T> entityClass) throws DataAccessException {
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(entityClass);
return criteria.list();
}
#Controller
#RequestMapping(value = "/genCompanyInfoUpdate", method = RequestMethod.POST)
public String genCompanyInfoUpdate(Model model) {
List<GenCountryModel> countryList=pt.getAll(GenCountryModel.class);
List<GenCurrencyModel> currencyList=pt.getAll(GenCurrencyModel.class);
GenCompanyInfoModel companyInfo=pt.getById(GenCompanyInfoModel.class, 1);
model.addAttribute("countryList", countryList);
model.addAttribute("currencyList", currencyList);
model.addAttribute("companyInfo", companyInfo);
return "gen/genCompanyInfoUpdate";
}
JSP
<c:if test="${not empty currencyList}">
<c:forEach items="${currencyList}" var="get" varStatus="counter">
<ct:Options setValue="${get.id}" setName="${get.isoCode}" selected="${companyInfo.genCurrencyModel.id}" setState="1" />
</c:forEach>
</c:if>
All working well but when I change and use Projection in Method as the following , then it give exception
java.lang.numberformatexception for input string id
java.lang.numberformatexception for input string isoCode
Changes: ProjectionList use in Method
#SuppressWarnings("unchecked")
public <T> List<T> getAll(Class<T> entityClass, String[] nameList) throws DataAccessException {
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(entityClass);
ProjectionList pl = Projections.projectionList();
for (int i=0; i<nameList.length; i++) {
pl.add(Projections.property(nameList[i].toString()));
}
criteria.setProjection(pl);
return criteria.list();
}
Changes in #Controller passing List [GenCurrencyModel]
#RequestMapping(value = "/genCompanyInfoUpdate", method = RequestMethod.POST)
public String genCompanyInfoUpdate(Model model) {
String []list={"id","isoCode"};
List<GenCountryModel> countryList=pt.getAll(GenCountryModel.class);
List<GenCurrencyModel> currencyList=pt.getAll(GenCurrencyModel.class,list);
GenCompanyInfoModel companyInfo=pt.getById(GenCompanyInfoModel.class, 1);
model.addAttribute("countryList", countryList);
model.addAttribute("currencyList", currencyList);
model.addAttribute("companyInfo", companyInfo);
return "gen/genCompanyInfoUpdate";
}
Same JSP
<c:if test="${not empty currencyList}">
<c:forEach items="${currencyList}" var="get" varStatus="counter">
<ct:Options setValue="${get.id}" setName="${get.isoCode}" selected="${companyInfo.genCurrencyModel.id}" setState="1" />
</c:forEach>
</c:if>
GenCurrencyModel
public class GenCurrencyModel implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "CURRENCYID")
#GeneratedValue
private long id;
#Column(name = "CODE")
private String currencyCode;
#Column(name = "DESCRIPTION")
private String currencyDesc;
#Column(name = "ISACTIVE")
private int isActive;
#Column(name = "MADELETE")
private int markAsDelete;
#Column(name = "ISOCODE")
private String isoCode;
#Column(name = "CURRENCYUNIT")
private String currencyUnit;
#Column(name = "CONNECTOR")
private String connector;
#Column(name = "SUBUNIT")
private String subUnit;
#Column(name = "RECENTUSERID")
private long recentUserId;
#Column(name = "RECENTUSERIP")
private String recentUserIp;
#Column(name = "DATETIME")
private Date dateTime;
#Column(name = "ISUPDATED")
private int isUpdated;
private GenCompanyInfoModel genCompanyInfoModel;
public GenCurrencyModel() {
super();
}
//Getter Setter
}
I check the query from log file . it successfully execute
and when I remove the following line from jsp page, then there is no any exception
<ct:Options setValue="${get.id}" setName="${get.isoCode}"
Note: ct:Options is a custom JSP tag, that just print values, nothing special
After Projection the result of query is as follow
Hibernate: select this_.CURRENCYID as y0_, this_.ISOCODE as y1_ from GENCURRENCY this_
and both returning the list , and I have check both of size(), the size is also same !
Update me !
Typically, when using a projection list of specific properties in Hibernate, you won't be able to cast the query result as an entity type, at least not in the older versions of Hibernate I'm familiar with (i.e. 3.2.x). Instead, the default return type will be a List<Object[]> (when calling Criteria#list), where each array represents a tuple of the properties you specified in the projection list. (You can tell Hibernate to change the return type by giving the Criteria a ResultTransformer, but that may cause more confusion.) So instead of expecting partially-hydrated entities of type T and calling its getter methods (via JSTL expression), expect an array of Objects and get each property value by index (based on the order of the properties in the projection list).
Otherwise, it appears that you're passing the string values "id" and "isoCode" to your ct tag library (instead of the id and isoCode field values that you want), which I assume is expecting strings that can be parsed into numbers using something like Integer#parseInt(String), and this is causing the NumberFormatExceptions.
If this doesn't help, can you please provide more information? Specifically:
What are the property names you're specifying in the projection list?
What object types are those properties mapped as in the entity class? Providing the full entity mapping would help.
Is the ct:Options a custom JSP tag? If so, can you provide the logic of the tag class?

Resources