BeanPropertyRowMapper when mapping for field not available - java

I have a repository class that calls jdbcTemplate query() and uses BeanPropertyRowMapper(..)
#Override
public List<Model> query(final Model model) {
return this.jdbcTemplate.query(
this.QUERY.replace("${WHERE}", this.queryBuilder.build(model)),
new PostGrePropertyMapper(model)
.addProperty("test", new TestMapper().apply(model.getTest())),
new BeanPropertyRowMapper<>(Model.class));
}
Say I have this model (it has many fields but for demo shortened to 3):
public class Model {
private String test;
private String id;
private String reference;
}
and the query returns me two columns: test & id in the ResultSet.
Is there a way I can set the value of the reference field to that of id that is being returned from database? The id and reference should be set off the id field coming from DB.
Where can I set this value for reference without having to write a custom row mapper and setting each and every field with rs.getString(...) calls.
Is there a short technique for such scenario?

The shortest technique is to fix the query so that it matches the model:
SELECT test, id, id as reference FROM ...

Related

JOOQ wont call DTO setter during custom data type conversion

I am using the JOOQ library in order to fetch the result from a select query into my custom DTO class. Because my Custom DTO class has an ENUM Type field mapped as an Integer Column in my database I am using a custom data type converter. The query I perform is just a basic select query:
public TestSuiteDto findByAuditTestSuiteId(Integer auditTestSuiteId) {
TestSuiteJTable ts = TestSuiteJTable.TEST_SUITE;
AuditTestSuiteJTable ats = AuditTestSuiteJTable.AUDIT_TEST_SUITE;
List<TestSuiteDto> result = dsl.select(
ts.ID,
ts.DESCRIPTION,
ts.PROFILE_TYPE_ID,
ts.CREATED,
ts.ACTIVE,
ts.DEPRECATION,
ts.FEEDBACK_QUESTIONNAIRE_ID)
.from(ts
.join(ats).on(ts.ID.eq(ats.TEST_SUITE_ID)))
.where(ats.ID.eq(auditTestSuiteId))
.fetchInto(TestSuiteDto.class);
//do some stuff before returning
return result.get(0);
}
My custom DTO class looks like this
#Data
public class TestSuiteDto {
private Integer id;
private String description;
private ProfileType profileType;
private LocalDateTime created;
private boolean active;
private LocalDateTime deprecation;
private Integer feedbackQuestionnaireId;
}
The problem is that during the fetching process the SETTER of the DTO class is never triggered for the ENUM type field e.g. profileType even though I have configured a custom data type converter:
#Slf4j
public class ProfileTypeConverter implements Converter<Integer, ProfileType> {
#Override
public ProfileType from(Integer databaseObject) {
log.info("ProfileTypeConverter.from {} -> {}", databaseObject, ProfileType.getFromId(databaseObject));
return ProfileType.getFromId(databaseObject);
}
#Override
public Integer to(ProfileType userObject) {
log.info("ProfileTypeConverter.to");
return userObject.getId();
}
#Override
public Class<Integer> fromType() {
log.info("ProfileTypeConverter.fromType");
return Integer.class;
}
#Override
public Class<ProfileType> toType() {
log.info("ProfileTypeConverter.toType");
return ProfileType.class;
}
}
I have added some logs just to check if the converter is triggered at all and I see that the converter is triggered as expected (from method of the Converter class is called during the execution of the JOOQ SQL query). I have also delombok my DTO class in order to add logs in SETTERs and GETTERs and see if those are also properly called. I found out that all the SETTERs are properly called except for the profileType one. Because of that when I retrieve the DTO from the result list the value of the profileType field is null. The column in my database (mysql) that maps to the profileType ENUM is called PROFILE_TYPE_ID and it is of type Integer. I have also configured a forcedType in the pom.xml following the examples on JOOQ documentation webpage.
<forcedType>
<includeExpression>${jdbc.database}.TEST_SUITE.PROFILE_TYPE_ID</includeExpression>
<userType>mypackage.type.ProfileType</userType>
<converter>mypackage.converter.ProfileTypeConverter</converter>
</forcedType>
and this is how I have configured the ProfileType Field in pom.xml
<field>
<expression>${jdbc.database}.TEST_SUITE.PROFILE_TYPE_ID</expression>
<fieldIdentifier>
<expression>PROFILE_TYPE_ID</expression>
</fieldIdentifier>
<fieldMember>
<expression>profileType</expression>
</fieldMember>
<fieldGetter>
<expression>getProfileType</expression>
</fieldGetter>
<fieldSetter>
<expression>setProfileType</expression>
</fieldSetter>
</field>
JOOQ version: 3.14.16, Java 8
Why do things behave this way?
The reason is that you have a name mismatch:
Query
ts.PROFILE_TYPE_ID,
DTO
private ProfileType profileType;
If you want to rely on the reflection based DefaultRecordMapper, then you must name those things accordingly, otherwise, they won't be mapped. The fact that you have a converter is irrelevant, if the names don't match. Imagine you had 20 columns of type ProfileType. You wouldn't want to have DefaultRecordMapper map values purely based on their type.
Regarding your comments:
I have added some logs just to check if the converter is triggered at all and I see that the converter is triggered as expected (from method of the Converter class is called during the execution of the JOOQ SQL query)
Yes of course. The Converter belongs to the projected column. The conversion happens before the mapping (i.e. the into(TestSuiteDto.class) call)
Solutions
There are multiple alternatives to solve this:
Call your DTO attribute profileTypeId, or to add JPA annotations to it to map between SQL names and Java names
Rename your SQL column (in DDL)
Alias your SQL column using PROFILE_TYPE_ID.as("profile_type")
Use a computed column PROFILE_TYPE and attach the converter to that, keeping the PROFILE_TYPE_ID as it is (you can also use client side computed columns for that, in order not to affect your schema)
Use type safe constructor based mapping, rather than reflection based mapping, e.g. using fetch(Records.mapping(TestSuiteDto::new))
There are probably more possible solutions.

Java/SpringBoot Web app. Insert new row with auto-incremented id column

I am trying to store a new row using a few input lines on a web app into an SQL table. My jsp has all the input rows I need. However, I need to store the new object without inputting a new Id because it's auto incremented. I'm able to call my constructor to store everything else but the id.
my code for that section so far is:
#RequestMapping(value = "/save", method = RequestMethod.POST)
public ModelAndView save
//Index connect
(#RequestParam("id") String id, #RequestParam("type") String animalType,
#RequestParam("name") String animalName, #RequestParam("age") int animalAge){
ModelAndView mv = new ModelAndView("redirect:/");
AnimalConstruct newAnimal;
newAnimal.setAnimalType(animalType);
newAnimal.setAnimalName(animalName);
newAnimal.setAnimalAge(animalAge);
animals.save(newAnimal);
mv.addObject("animalList", animals.findAll());
return mv;
So if I wanted to store "(id)11, (type)bird, (name)patty, (age)5" and I'm only making the type, name, and age inputtable, what should I do for the id? The object technically injects the id as empty I think, but then I get thrown an error. I'm very new to java and Springboot and have very weak skills in both.
The magic happens with a JPA implementation (Hibernate, for instance). Just annotate your id field like:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
When saving the object, the id will be auto-generated and stored.
Check some similar questions: Hibernate Auto Increment ID and How to auto generate primary key ID properly with Hibernate inserting records
You should not pass the ID when you expect to create an object.
#RequestMapping(value = "/protected", method = RequestMethod.POST)
public RouteDocument doPost(#RequestBody RouteDocument route) throws ControllerException {
createNewRoute(route);
return route;
}
In the previous example, the method createNewRoute, calls the database, in my case using spring JpaTemplate to save it. The object route has an ID property that is filled by JpaTemplate.save. Consequently the doPost return object returns you the same object you passed as parameter BUT with the automatically assigned ID.
Annotate your id column in the bean with :
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private long id;
As answered by #pedrohreis above you can also use GenerationType.AUTO but only if your sole purpose is to make autoincrement id then I prefer GenerationType.IDENTITY
Also, looking forward in your project if you wanna disables batch updates on your data then you should use GenerationType.IDENTITY.
Refer : hibernate-identifiers

Change table on runtime - Spring API Rest

Now, I have the next entity. This one is the m1 table of my database.
#Entity(name = "m1")
#Data
public class Information {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String date;
private Double weight_1;
private Double weight_2;
private Double weight_3;
private Double weight_4;
private int working;
}
So, when I do some call to the APIRest it returns me the information corresponding to the m1 table. The controller that I have is the next (simple controller that returns all the information):
#Controller
#RequestMapping(path = "/information")
public class InformationController {
#Autowired
private InformationRepository repository;
#GetMapping(path="/all")
public #ResponseBody List<Information> getAllInformations() {
// This returns a JSON or XML with the users
return repository.findAll();
}
}
The question is: There is any way to change the name of the m1 on runtime. For example can I put the name of the table in the call path and in the API Rest take it?
Maybe this is impossible and I am doing it the bad way I do not know.
EDIT: I mean, can I change the table that the API Rest is taking the data by putting the table that I want in the url/path that I call. For example: in my case the default table/entity that the APIRest take the data is m1, so can I call http://localhost:8080/information/especifictable/all/ where especific table is the table that I want the recieve the data of the database and in the API Rest take that url parameter and change the default m1 with the especifictable.
I do not know if I have explained it well, I do not know how to explain it well.
Such a design would only make sense, if there are two tables in DB, which look the same. if that is the case there is something wrong with your DB design.
Basically it is not possible, to the best of my knowledge.

Neo4J OGM Session.load(ID) returns null object for existing ID

I am conducting some Neo4J tests and running into the following peculiar problem. I created a small model which I'm intending to use with OGM. The model has a superclass Entity and a child class Child. They're both in package persistence.model. Entity has the required Long id; with matching getId() getter.
public abstract class Entity {
private Long id;
public Long getId() {
return id;
}
}
#NodeEntity
Child extends Entity {
String name;
public Child() {
}
}
Creating Child objects and persisting them through OGM works fine. I'm basing myself on the examples found in the documentation and using a Neo4jSessionFactory object, which initialises the SessionFactory with the package persistence.model. The resulting database contains objects with proper ID's filled in.
The problem arises when I try to fetch a Child for a given ID. I'm trying it with three methods, using two connection systems (bolt and ogm):
boltSession.run("MATCH (a:Child) WHERE id(a) = {id} RETURN a", parameters("id", childId));
ogmSession.query("MATCH (a:Child) WHERE id(a) = $id RETURN a", params);
ogmSession.load(Child.class, childId, 1);
The first two methods actually return the correct data. The last one returns a null value. The last one, using OGM, has some obvious benefits, and I'd love to be able to use it properly. Can anyone point me in the right direction?
In your test code you are doing a lookup by id of type int.
private int someIdInYourDatabase = 34617;
The internal ids in Neo4j are of type Long.
If you change the type of the id to long or Long then it will work.
private long someIdInYourDatabase = 34617;

Updating with Morphia Optimistic locking

Hi considering the following example:
Resource:
#PUT
#Path("{id}")
public Response update(#PathParam(value = "id") final String id, final Person person) {
final Person person = service.getPerson(id);
final EntityTag etag = new EntityTag(Integer.toString(person.hashCode()));
// If-Match is required
ResponseBuilder builder = request.evaluatePreconditions(etag);
if (builder != null) {
throw new DataHasChangedException("Person data has changed: " + id);
}
service.updatePerson(id, person.getName());
....
}
Service:
public void updatePerson(final String id, final String name) {
final Query<Person> findQuery = morphiaDataStore.createQuery(Person.class).filter("id ==", id);
UpdateOperations<Person> operation = morphiaDataStore.createUpdateOperations(Person.class).set("name", name);
morphiaDataStore.findAndModify(findQuery, operation );
}
Person:
#Entity("person")
public class Person {
#Id
private ObjectId id;
#Version
private Long version;
private String name;
...
}
I do check if the etag provided is the same of the person within the database. However this check is been done on the resource itself. I don't think that this is safe since the update happens after the check and another thread could have gone threw the check in the meantime. How can this be solved correctly? Any example or advise is appreciated.
Morphia already implements optimistic-locking via #Version annotation.
http://mongodb.github.io/morphia/1.3/guides/annotations/#version
#Version marks a field in an entity to control optimistic locking. If the versions change in the database while modifying an entity (including deletes) a ConcurrentModificationException will be thrown. This field will be automatically managed for you – there is no need to set a value and you should not do so. If another name beside the Java field name is desired, a name can be passed to this annotation to change the document’s field name.
I see you have already use the annotation in your example. Make sure the clients include the version of the document as part of the request so you can also pass it to morphia.
Not sure if findAndModify will be able to handle it (I would think it does). but at least I'm sure save does handle it.
Assuming the object person contains the new name and version that the client was looking at, you can do directly something like this to update the record:
morphiaDataStore.save(person);
If there was another save before this client could pick it up the versions will no longer match and a ConcurrentModificationException will be issued with this message:
Entity of class %s (id='%s',version='%d') was concurrently updated

Categories