Jdbc Template set List Object properties with row mapper - java

I was reading this post :
JDBCTemplate set nested POJO with BeanPropertyRowMapper
This answer seems great : JDBCTemplate set nested POJO with BeanPropertyRowMapper
However another situation came to my mind and I was not able until now to find suitable answer :
What if I got a list of Users in my message Class instead :
class User {
Long id;
String name;
}
class Message {
Long id;
String name;
List<User> users;
}
Could it be even possible with sql request manually (no jpa or crud repository) and row mapper to map every User for One Message and to make something like ? :
public class MessageMapper implements RowMapper<Message> {
public Message mapRow(ResultSet rs, int i) throws SQLException{
Message message= new Message();
message.setId(rs.getLong("messageId");
message.setName(rs.getString("messageName"));
User user = new User();
// or List<User> users = new ArrayList<>(); ??? ...
user.setUsers(users);
return message ;
}}
Thank you !

Related

Jersey and Object Mapper

I am a bit confused as to when I need to use an object mapper. I thought it should be used for mapping a result set from a DB query into objects so I created an object mapper like this:
public class PersonMapper implements ResultSetMapper<Person>
{
public Person map(int index, ResultSet resultSet, StatementContext ctx) throws SQLException
{
Person person = new Person();
person.setPersonId(resultSet.getShort("PersonId"));
person.setPersonType((PersonType) resultSet.getObject("PersonType"));
person.setPersonName(resultSet.getString("PersonName"));
person.setPersonMobile(resultSet.getString("PersonMobile"));
return person;
}
}
Then I registered it with the specific DAO like this: #RegisterMapper(PersonMapper.class)
However, it seems that everything also works without the mapper even if I make a query like this: List<Person> list = list(namedQuery("Person.findAll")); which returns a proper list.
So when exactly should I use a mapper?
if you are talking about ObjectMapper (com.fasterxml.jackson.databind.ObjectMapper) class then we use it to save an entire POJO object as JSON string into the database, in which you need to keep the data type of the column as json while table creation...
so we use it like this
public Response saveStateRules(#Context HttpServletRequest request,StatePojo statePojo)
{
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(statePojo);
State state = new State();
state.setRulejson(json);
}
where,
public class StatePojo implements Serializable{
private Integer stateid;
private ArrayList<StateRuleCondition> fieldconditions;
private ArrayList<Integer> stateids;
private Boolean isallow;
// contains all getters and setters
}

how to define a keyspace dynamically in accessor

I am attempting to create an accessor to run slightly more complex queries in cassandra with java. I have no problem with the syntax, and I can get it to work, but my question is this: is there a way to dynamically declare a keyspace in an accessor?
For example, if you create a table map for the MappingManager you would declare the #Table and give it the keyspace and table name like so:
#Table(keypace="mykeyspace", name="orders")
public class Orders {
#PartitionKey
public UUID id;
//blah blah blah, rest of code
}
Now creating an accessor for that specific table is easy enough:
#Accessor
public interface OrdersAccessor {
#Query("SELECT * FROM orders WHERE status = :status")
Result pending(#Param("status") Integer status);
}
Simple. The problem is it demands a keyspace, and I am a huge fan of never hard-coding anything. I realize that I am "hard-coding" the keyspace in the Table definition in the MappingManager class definition, but if need be I only change it there and it updates everything that has to do with that. If I hard-code the keyspace in every single #Query definition inside the Accessor I will have to change, potentially, a bunch of different items if the keyspace gets updated, instead of only changing it one place in the #Table definition.
I have been searching Google for hours and I can't find a single instance of someone dynamically declaring a keyspace with an accessor, only thousands of examples of accessors where they are hard-coding the keyspace into the #Query like so:
#Accessor
public interface OrdersAccessor {
#Query("SELECT * FROM keyspace.orders WHERE status = :status")
Result pending(#Param("status") Integer status);
}
I realize the query I wrote isn't really cause for an accessor, I was just simplifying it for the sake of the example. So I am coming to the community asking for help, I can't find any examples of this anywhere. I can't imagine that I am the first person to ever want to do this, I just can't find any examples of anyone else tackling this problem. Thank you in advance for any help you can give, I can really use it.
#Sudhir Here is the solution I came up with. I am sure there are better ways to handle the connections, but I am still pretty new to cassandra and Java, and this is working well for my needs. I hope this helps...
public class DbInterface {
private Cluster cluster;
private Session session;
private Map<String, Session> dbMap;
private Map<String, Map<String, Mapper<Class>>> mappers = new ConcurrentHashMap<>();
public DbInterface(String host) {
Map<String, Session> connections = createConnection(host);
Session crSession = connections.get("crSession");
Session hppSession = connections.get("hppSession");
cluster = Cluster.builder().addContactPoint(host).build();
Session crSession = cluster.connect("mykeyspace");
Session hppSession = cluster.connect("hpp");
MappingManager crManager = new MappingManager(crSession);
MappingManager hppManager = new MappingManager(hppSession);
mappers.put("mykeyspace", new ConcurrentHashMap<>());
mappers.put("mykeyspace2", new ConcurrentHashMap<>());
Map cr = mappers.get("mykeyspace");
Map hpp = mappers.get("mykeyspace2");
cr.put("status", crManager.mapper(OrderStatus.class));
hpp.put("status", hppManager.mapper(OrderStatus.class));
cr.put("status_accessor", crManager.createAccessor(OrderStatusAccessor.class));
hpp.put("status_accessor", hppManager.createAccessor(OrderStatusAccessor.class));
cr.put("users", crManager.mapper(Users.class));
hpp.put("users", hppManager.mapper(Users.class));
cr.put("orders", crManager.mapper(Orders.class));
hpp.put("orders", hppManager.mapper(Orders.class));
cr.put("order_detail", crManager.mapper(OrderDetail.class));
hpp.put("order_detail", hppManager.mapper(OrderDetail.class));
cr.put("chal_orders", crManager.mapper(ChalOrder.class));
hpp.put("chal_orders", hppManager.mapper(ChalOrder.class));
cr.put("chal_order_detail", crManager.mapper(ChalOrderDetail.class));
hpp.put("chal_order_detail", hppManager.mapper(ChalOrderDetail.class));
cr.put("detail_accessor", crManager.createAccessor(OrderDetailAccessor.class));
hpp.put("detail_accessor", hppManager.createAccessor(OrderDetailAccessor.class));
cr.put("tracking_labels", crManager.mapper(TrackingLabels.class));
hpp.put("tracking_labels", hppManager.mapper(TrackingLabels.class));
}
public Session getConnection(String type) {
if(dbMap.containsKey(type)) {
return dbMap.get(type);
}
if(dbMap.containsKey(type.toLowerCase() +"Session")) {
return dbMap.get(type.toLowerCase() +"Session");
}
return dbMap.get("crSession");
}
public Map<String, Session> createConnection(String host) {
dbMap = new HashMap<>();
cluster = Cluster.builder().addContactPoint(host).build();
Session crSession = cluster.connect("mykeyspace");
Session hppSession = cluster.connect("hpp");
dbMap.put("crSession", crSession);
dbMap.put("hppSession", hppSession);
return dbMap;
}
public Map getDBMap(String client) {
if(mappers.containsKey(client)) {
return mappers.get(client);
}
throw new RuntimeException("Unknown Client: " + client);
}
}
One of the things I was thinking of doing is moving the session creation and Map creation to separate functions, then only connect and build the map for the session that is needed. Like instead of defaulting to connecting to both sessions when the DbInterface() is called, only connect to the session that is requested via the "host" param.
Anywho, I hope this helps you out. If you need it, here is an example of my other library that uses this...
public class MyRestController {
private final DbInterface db = new DbInterface(IPADDRESS);
#CrossOrigin
#RequestMapping("/status")
public String getStatus() {
Map managerMap = db.getDBMap("mykeyspace");
OrderStatusAccessor statuses = (OrderStatusAccessor) managerMap.get("status_accessor");
Result<OrderStatus> allStatuses = statuses.getAll();
//rest of the code here
}
}

Spring Boot with JDBC: Nested Object Modelling

i have the following TableStructure in a PostgreSQL DB which is supposed to be the DB Backend for my WebApp:
init_db.sql
CREATE TABLE article (
id integer NOT NULL,
name character varying NOT NULL,
type_id integer NOT NULL
);
CREATE TABLE article_type (
id integer NOT NULL,
type_desc character varying NOT NULL
);
ALTER TABLE ONLY article
ADD CONSTRAINT
article_type_id_fkey FOREIGN KEY (type_id) REFERENCES article_type(id);
The basic access to this works (via DataSource Object defined in application.properties and letting Spring Boot handle the rest). I'm having now difficulties in understanding how to access/model this best in Spring Boot. Currently my Model Classes look like this:
ArticleType.java
public class ArticleType {
private Integer id;
private String name;
// Getters and Setters
}
andArticle.java
public class Article {
private Integer id;
private String name;
private String desc;
private ArticleType article_type;
// Getters and Setters
}
Following this example, i was constructing those classes:
ArticleTypeRepository.java
#Repository
public class ArticleTypeRepository {
#Autowired
protected JdbcTemplate jdbc;
public ArticleType getArticleType(int id) {
return jdbc.queryForObject("SELECT * FROM article.article_type WHERE id=?", articleTypeMapper, id);
}
private static final RowMapper<ArticleType> articleTypeMapper = new RowMapper<ArticleType>() {
public ArticleType mapRow(ResultSet rs, int rowNum) throws SQLException {
ArticleType articletype = new ArticleType();
articletype.setId(rs.getInt("id"));
articletype.setName(rs.getString("type_desc"));
return articletype;
}
};
and for the following file my question arises:ArticleRepository.java
#Repository
public class ArticleRepository {
#Autowired
protected JdbcTemplate jdbc;
public Article getArticle(int id) {
return jdbc.queryForObject("SELECT * FROM article.article WHERE id=?", articleMapper, id);
}
private static final RowMapper<Article> articleMapper = new RowMapper<Article>() {
public Article mapRow(ResultSet rs, int rowNum) throws SQLException {
Article article = new Article();
article.setId(rs.getInt("id"));
article.setName(rs.getString("name"));
// The following line is the one in question
// ArticleType at = getArticleType(Integer.parseInt(rs.getString("type_id")));
article.setArticle_type(at);
article.setDesc(rs.getString("description"));
return article;
}
};
What is the best practice to get the ArticleType here for the Article? Is this anyway good practice to retrieve those objects? Or should I just use a plain String object in the Article Object and query this with a view or something? I looked through the internet for "Spring Boot JDBC Nested Object Java Access Modeling" and the alike, but couldn't find any real hints or tutorials to this specific question, which makes me wonder if i'm doing something conceptually completely wrong. Any hints are appreciated (tutorials, doc's, paradigms how to do this properly, etc.)
I'll double post M. Deinum 's answer here, since it got me rolling until i switched to Hibernate/JPA:
By creating a query that returns everything you need. Write a select
statement that joins both tables.

External object linked through foreign key in hibernate and MySql

I'm using Spring data with Hibernate and MySql and I have a doubt.
My entity is
#Entity
#Table(name = "car", catalog = "DEMO")
public class Car implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer idCar;
#JsonBackReference
private CarType carType;
#JsonBackReference
private Fleet fleet;
private String id;
private int initialKm;
private String carChassis;
private String note;
#JsonManagedReference
private Set<Acquisition> acquisitions = new HashSet<Acquisition>(0);
with get and set method.
Sometimes, I need external object as carType, another entity.
If I use this webservice
#Override
#RequestMapping(value = { "/cars/{idFleet}"}, method = RequestMethod.GET)
public String getCars(#PathVariable int idFleet, Model model){
try{
model.addAttribute("carsList",fleetAndCarService.findCarsByIdFleet(idFleet));
//Modal parameter
model.addAttribute("carTypeList",fleetAndCarService.getCarsType());
model.addAttribute("fleetApplication",fleetAndCarService.getFleetById(idFleet));
model.addAttribute("carForm", new CarForm());
model.addAttribute("error",false);
}catch (Exception e){
LOG.error("Threw exception in FleetAndCarControllerImpl::getCars : " + ErrorExceptionBuilder.buildErrorResponse(e));
model.addAttribute("error",true);
}
return "cars";
}
from my html page I can retrieve carType.idCarType,but if I use this
#Override
#RequestMapping(value = { "/cars/{idFleet}"}, method = RequestMethod.GET)
public #ResponseBody TableUI getCars(#PathVariable int idFleet) {
TableUI ajaxCall=new TableUI();
try {
ajaxCall.setData(fleetAndCarService.findCarsByIdFleet(idFleet));
return ajaxCall;
} catch (QueryException e) {
ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(e);
LOG.error("Threw exception in FleetAndCarControllerImpl::addCar :" + errorResponse.getStacktrace());
return ajaxCall;
}
}
where TableUi has only a field data where I put the result to use it into datatables, I don't have carType and fleet. Why? Do I have to use Hibernate.initialize, and how so it is a list?Thansk,regards
Also this update doesn't work:
#Override
#Transactional
public List<Car> findByFleetIdFleet(int idFleet) {
List<Car> carList= carRepository.findByFleetIdFleet(idFleet);
for (Car car:carList)
Hibernate.initialize(car.getCarType());
return carList;
}
You could call Hibernate.initialize on each element
Collection<Car> cars = fleetAndCarService.findCarsByIdFleet(idFleet);
for(Car car : cars) {
Hibernate.initialize(car.getCarType());
Hibernate.initialize(car.getFleet());
}
ajaxCall.setData();
return ajaxCall;
This would be a good starting point and would allow you to move forwards. At high scales however this could become a performance bottleneck as it will perform a query with each call to initialize so you will have 2*n queries to the database.
For maximum performance you will have several other options:
Iterate through the cars and build up a list of IDs and then query for the car types by ID in a single query with the list of IDs. Do the same for the fleets. Then call Hibernate.initialize. The first two queries will populate the persistence context and the call to initialize will not need to go to the database.
Create a special query for this call which fetch joins the properties you will need.
Setup batch fetching which will fetch the cards and fleets in batches instead of one car/fleet per query.
Use a second level cache so the initialization causes Hibernate to pull from the cache instead of the database.
Describing these options in details is beyond the scope of a single question but a good place to start would be Hibernate's documentation on performance.

How to get old entity value in #HandleBeforeSave event to determine if a property is changed or not?

I'm trying to get the old entity in a #HandleBeforeSave event.
#Component
#RepositoryEventHandler(Customer.class)
public class CustomerEventHandler {
private CustomerRepository customerRepository;
#Autowired
public CustomerEventHandler(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
#HandleBeforeSave
public void handleBeforeSave(Customer customer) {
System.out.println("handleBeforeSave :: customer.id = " + customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
Customer old = customerRepository.findOne(customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
System.out.println("handleBeforeSave :: old customer.name = " + old.getName());
}
}
In the event I try to get the old entity using the findOne method but this return the new event. Probably because of Hibernate/Repository caching in the current session.
Is there a way to get the old entity?
I need this to determine if a given property is changed or not. In case the property is changes I need to perform some action.
If using Hibernate, you could simply detach the new version from the session and load the old version:
#RepositoryEventHandler
#Component
public class PersonEventHandler {
#PersistenceContext
private EntityManager entityManager;
#HandleBeforeSave
public void handlePersonSave(Person newPerson) {
entityManager.detach(newPerson);
Person currentPerson = personRepository.findOne(newPerson.getId());
if (!newPerson.getName().equals(currentPerson.getName)) {
//react on name change
}
}
}
Thanks Marcel Overdijk, for creating the ticket -> https://jira.spring.io/browse/DATAREST-373
I saw the other workarounds for this issue and want to contribute my workaround as well, cause I think it´s quite simple to implement.
First, set a transient flag in your domain model (e.g. Account):
#JsonIgnore
#Transient
private boolean passwordReset;
#JsonIgnore
public boolean isPasswordReset() {
return passwordReset;
}
#JsonProperty
public void setPasswordReset(boolean passwordReset) {
this.passwordReset = passwordReset;
}
Second, check the flag in your EventHandler:
#Component
#RepositoryEventHandler
public class AccountRepositoryEventHandler {
#Resource
private PasswordEncoder passwordEncoder;
#HandleBeforeSave
public void onResetPassword(Account account) {
if (account.isPasswordReset()) {
account.setPassword(encodePassword(account.getPassword()));
}
}
private String encodePassword(String plainPassword) {
return passwordEncoder.encode(plainPassword);
}
}
Note: For this solution you need to send an additionally resetPassword = true parameter!
For me, I´m sending a HTTP PATCH to my resource endpoint with the following request payload:
{
"passwordReset": true,
"password": "someNewSecurePassword"
}
You're currently using a spring-data abstraction over hibernate.
If the find returns the new values, spring-data has apparently already attached the object to the hibernate session.
I think you have three options:
Fetch the object in a separate session/transaction before the current season is flushed. This is awkward and requires very subtle configuration.
Fetch the previous version before spring attached the new object. This is quite doable. You could do it in the service layer before handing the object to the repository. You can, however not save an object too an hibernate session when another infect with the same type and id it's known to our. Use merge or evict in that case.
Use a lower level hibernate interceptor as described here. As you see the onFlushDirty has both values as parameters. Take note though, that hibernate normally does not query for previous state of you simply save an already persisted entity. In stead a simple update is issued in the db (no select). You can force the select by configuring select-before-update on your entity.
Create following and extend your entities with it:
#MappedSuperclass
public class OEntity<T> {
#Transient
T originalObj;
#Transient
public T getOriginalObj(){
return this.originalObj;
}
#PostLoad
public void onLoad(){
ObjectMapper mapper = new ObjectMapper();
try {
String serialized = mapper.writeValueAsString(this);
this.originalObj = (T) mapper.readValue(serialized, this.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}
I had exactly this need and resolved adding a transient field to the entity to keep the old value, and modifying the setter method to store the previous value in the transient field.
Since json deserializing uses setter methods to map rest data to the entity, in the RepositoryEventHandler I will check the transient field to track changes.
#Column(name="STATUS")
private FundStatus status;
#JsonIgnore
private transient FundStatus oldStatus;
public FundStatus getStatus() {
return status;
}
public FundStatus getOldStatus() {
return this.oldStatus;
}
public void setStatus(FundStatus status) {
this.oldStatus = this.status;
this.status = status;
}
from application logs:
2017-11-23 10:17:56,715 CompartmentRepositoryEventHandler - beforeSave begin
CompartmentEntity [status=ACTIVE, oldStatus=CREATED]
Spring Data Rest can't and likely won't ever be able to do this due to where the events are fired from. If you're using Hibernate you can use Hibernate spi events and event listeners to do this, you can implement PreUpdateEventListener and then register your class with the EventListenerRegistry in the sessionFactory. I created a small spring library to handle all of the setup for you.
https://github.com/teastman/spring-data-hibernate-event
If you're using Spring Boot, the gist of it works like this, add the dependency:
<dependency>
<groupId>io.github.teastman</groupId>
<artifactId>spring-data-hibernate-event</artifactId>
<version>1.0.0</version>
</dependency>
Then add the annotation #HibernateEventListener to any method where the first parameter is the entity you want to listen to, and the second parameter is the Hibernate event that you want to listen for. I've also added the static util function getPropertyIndex to more easily get access to the specific property you want to check, but you can also just look at the raw Hibernate event.
#HibernateEventListener
public void onUpdate(MyEntity entity, PreUpdateEvent event) {
int index = getPropertyIndex(event, "name");
if (event.getOldState()[index] != event.getState()[index]) {
// The name changed.
}
}
Just another solution using model:
public class Customer {
#JsonIgnore
private String name;
#JsonIgnore
#Transient
private String newName;
public void setName(String name){
this.name = name;
}
#JsonProperty("name")
public void setNewName(String newName){
this.newName = newName;
}
#JsonProperty
public void getName(String name){
return name;
}
public void getNewName(String newName){
return newName;
}
}
Alternative to consider. Might be reasonable if you need some special handling for this use-case then treat it separately. Do not allow direct property writing on the object. Create a separate endpoint with a custom controller to rename customer.
Example request:
POST /customers/{id}/identity
{
"name": "New name"
}
I had the same problem, but I wanted the old entity available in the save(S entity) method of a REST repository implementation (Spring Data REST).
What I did was to load the old entity using a 'clean' entity manager from which I create my QueryDSL query:
#Override
#Transactional
public <S extends Entity> S save(S entity) {
EntityManager cleanEM = entityManager.getEntityManagerFactory().createEntityManager();
JPAQuery<AccessControl> query = new JPAQuery<AccessControl>(cleanEM);
//here do what I need with the query which can retrieve all old values
cleanEM.close();
return super.save(entity);
}
The following worked for me. Without starting a new thread the hibernate session will provide the already updated version. Starting another thread is a way to have a separate JPA session.
#PreUpdate
Thread.start {
if (entity instanceof MyEntity) {
entity.previous = myEntityCrudRepository.findById(entity?.id).get()
}
}.join()
Just let me know if anybody would like more context.
Don't know if you're still after an answer, and this is probably a bit 'hacky', but you could form a query with an EntityManager and fetch the object that way ...
#Autowired
EntityManager em;
#HandleBeforeSave
public void handleBeforeSave(Customer obj) {
Query q = em.createQuery("SELECT a FROM CustomerRepository a WHERE a.id=" + obj.getId());
Customer ret = q.getSingleResult();
// ret should contain the 'before' object...
}

Categories

Resources