How to transfer objectId to other changesets in mongock? - java

I'm trying to transfer the previously created ID to other changesets in mongock. This is my solution but I don't know if it's correct. I will be grateful if you could share your proposition.
#ChangeLog(order = "001")
public class DatabaseChangelock {
private Map<String, ObjectId> myIdMap = new HashMap<>();
#ChangeSet(order = "001", id = "logs001", author = "Me")
public void userTest(UserEntityRepo user) {
UserEntity userEntity = new UserEntity;
userEntity.setName("Mike");
userEntity.setSurname("Smith");
user.save(userEntity);
myIdMap.put("test",userEntity.getId());
}
#ChangeSet(order = "002", id = "logs002", author = "Me")
public void factoryTest(FactoryEntityRepo factory) {
FactoryEntity factoryEntity = new FactoryEntity;
factoryEntity.setName("BigCompany");
factoryEntity.setBossId(myIdMap.get("test"));
factory.save(factoryEntity);
}
}

If you are considering to use transactions, I assume your MongoDB database is 4.0 or higher.
In that case I suggest you to follow the official MongoDB and Spring documentation and then just follow the Mongock documentation to tell it to use transactions.
Please notice that right now, Mongock only supports all-or-nothing transactions, this means that just one transaction is created to the entire migration.
We are working to provide different levels of transactions as well as other mechanisms to help in non-transactional environments.

Related

How does JPA EntityGraph allow chosing the entity graph at runtime?

I have seen various post describing that JPA EntityGraph allows to choose the graph at run time. And I am not entirely clear what this refers to.
Out of good faith and respect I would like to take this helpful article for reference: https://www.baeldung.com/jpa-entity-graph. (Most of JPA users might have gone through it already.)
The article quotes -
EntityGraph allows grouping the related persistence fields which we
want to retrieve and lets us choose the graph type at runtime.
and again solidifies above statement in conclusion section.
In this article, we've explored using the JPA Entity Graph to
dynamically fetch an Entity and its associations.
The decision is made at runtime in which we choose to load or not the
related association.
As we see in the article (5.1) - EntityGraphs are defined as below using Annotations-
5.1. Defining an Entity Graph with Annotations
#NamedEntityGraph(
name = "post-entity-graph",
attributeNodes = {
#NamedAttributeNode("subject"),
#NamedAttributeNode("user"),
#NamedAttributeNode("comments"),
}
)
#Entity
public class Post {
#OneToMany(mappedBy = "post")
private List<Comment> comments = new ArrayList<>();
//...
}
The #NameEntityGraph annotation is defined at compile time and I don't see anything runtime or dynamic here.
But in 5.2 - entity graphs are defined using api or programmatically -
5.2. Defining an Entity Graph with the JPA API
EntityGraph<Post> entityGraph = entityManager.createEntityGraph(Post.class);
entityGraph.addAttributeNodes("subject");
entityGraph.addAttributeNodes("user");
In 5.2 approach, I see nodes can be chosen dynamically using some logic. So is this approach is what is refered to "dynamically fetch" and "runtime based".
Or am i missing something and do i have more to understand.
Further the approaches given in 6. Using the Entity Graph
ex:
EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph");
Map<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.fetchgraph", entityGraph);
Post post = entityManager.find(Post.class, id, properties);
are all programmatic and so can be changed during runtime i.e they can be said as dynamic.
But one approach missed in above article, but mentioned here - https://www.baeldung.com/spring-data-jpa-named-entity-graphs, as below, does not seem to fit in to dynamic criteria.
public interface ItemRepository extends JpaRepository<Item, Long> {
#EntityGraph(value = "Item.characteristics")
Item findByName(String name);
}
So does the dynamic approach just refer to 5.2 style or it implies even 5.1 style too.
You can't use dynamic entity graphs with spring-data, because JpaRepository doesn't have methods to pass entity graphs like
Optional<T> findById(ID id, EntityGraph entityGraph);
Using custom JPA repository
You can use raw JPA for that, by creating a custom repository and using entity graphs with EntityManager.
Using spring-data-jpa-entity-graph
There is a more convenient approach by using library
spring-data-jpa-entity-graph.
It allows to use JPA repository methods like findById() or findByName() with dynamic entity graphs.
I prefer to use it with this helper class
public abstract class EntityGraphBuilder<T> {
private List<String> result = new ArrayList<>();
protected T self;
public T add(String path) {
result.add(path);
return self;
}
public DynamicEntityGraph build() {
return new DynamicEntityGraph(EntityGraphType.FETCH, result);
}
}
Each entity has its own GraphBuilder
#Entity
public class OrderEntity {
#Id
private Long id;
#Column
private name;
#ManyToOne(fetch = FetchType.LAZY)
private OrderRequestEntity orderRequest;
#ManyToOne(fetch = FetchType.LAZY)
private ProviderEntity provider;
public static GraphBuilder graph() {
return new GraphBuilder();
}
public static class GraphBuilder extends EntityGraphBuilder<GraphBuilder> {
private GraphBuilder() {
self = this;
}
public GraphBuilder orderRequest() {
return add("orderRequest");
}
public GraphBuilder provider() {
return add("provider");
}
}
}
Repository uses EntityGraphJpaRepository from spring-data-jpa-entity-graph library
#Repository
public interface OrdersRepository extends EntityGraphJpaRepository<OrderEntity, Long> {
OrderEntity findByName(String name, EntityGraph entityGraph);
}
You can use derived query methods like findByName() with dynamic entity graphs too.
Example of using findById() method, the same approach can be applied to findByName()
OrdersRepository ordersRepository;
Long orderId = 1L;
OrderEntity order = ordersRepository.findById(
orderId,
OrderEntity.graph().orderRequest().provider().build()
).orElseThrow(
() -> new ServiceException("Can't find orderId=" + orderId)
);
In the Baeldung article, Section 5 is only about various ways to define a graph with not much emphasis on the dynamic/non-dynamic nature of the definition itself.
In Section 5.1 the definition of the graph is static but this section is only about demonstrating how to define a graph which then will be used in typical cases when building the graph dynamically is not really necessary. This section shows an alternative to the older way of building HQL / JPA-QL queries with JOIN FETCH sections.
#NamedEntityGraph(
name = "post-entity-graph",
attributeNodes = {
#NamedAttributeNode("subject"),
#NamedAttributeNode("user"),
#NamedAttributeNode("comments"),
}
)
#Entity
public class Post {
#OneToMany(mappedBy = "post")
private List<Comment> comments = new ArrayList<>();
//...
}
Then, Section 6 tells you how to use the entity graphs defined earlier in various ways.
// Getting the "statically" defined graph (from annotation)
EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph");
// Then using the graph
Map<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.fetchgraph", entityGraph);
Post post = entityManager.find(Post.class, id, properties);
Naturally, you can swap this first line to the fully dynamically built graph demonstrated in Section 5.2:
// Building the graph dynamically
EntityGraph<Post> entityGraph = entityManager.createEntityGraph(Post.class);
entityGraph.addAttributeNodes("subject");
entityGraph.addAttributeNodes("user");
// Then using the graph
Map<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.fetchgraph", entityGraph);
Post post = entityManager.find(Post.class, id, properties);
In both cases you supply an EntityGraph object to the query.

Using a Hibernate filter with Spring Boot JPA

I have found the need to limit the size of a child collection by a property in the child class.
I have the following after following this guide:
#FilterDef(name="dateFilter", parameters=#ParamDef( name="fromDate", type="date" ) )
public class SystemNode implements Serializable {
#Getter
#Setter
#Builder.Default
// "startTime" is a property in HealthHistory
#Filter(name = "dateFilter", condition = "startTime >= :fromDate")
#OneToMany(mappedBy = "system", targetEntity = HealthHistory.class, fetch = FetchType.LAZY)
private Set<HealthHistory> healthHistory = new HashSet<HealthHistory>();
public void addHealthHistory(HealthHistory health) {
this.healthHistory.add(health);
health.setSystem(this);
}
}
However, I don't really understand how to toggle this filter when using Spring Data JPA. I am fetching my parent entity like this:
public SystemNode getSystem(UUID uuid) {
return systemRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Could not find system with id " + uuid));
}
And this method in turn calls the Spring supported repository interface:
public interface SystemRepository extends CrudRepository<SystemNode, UUID> {
Optional<SystemNode> findByUuid(UUID uuid);
}
How can I make this filter play nicely together with Spring? I would like to activate it programatically when I need it, not globally. There are scenarios where it would be viable to disregard the filter.
I am using Spring Boot 1.3.5.RELEASE, I cannot update this at the moment.
Update and solution
I tried the following as suggested to me in the comments above.
#Autowired
private EntityManager entityManager;
public SystemNode getSystemWithHistoryFrom(UUID uuid) {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("dateFilter");
filter.setParameter("fromDate", new DateTime().minusHours(4).toDate());
SystemNode systemNode = systemRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Could not find system with id " + uuid));
session.disableFilter("dateFilter");
return systemNode;
}
I also had the wrong type in the FilterDef annotation:
#FilterDef(name="dateFilter", parameters=#ParamDef( name="fromDate", type="timestamp" ) )
I changed from date to timestamp.
This returns the correct number of objects, verified against the database.
Thank you!

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
}
}

How to create relationships between objects in Spring JDBC?

I want to implement relationships from JPA to Spring JDBC. For instance, assume I have Account and Advert objects. The relationship between Account and Advert is #OneToMany according to JPA.
Account class:
public class Account {
private Long id;
private String username;
private Set<Advert> adverts = new HashSet<Advert>();
// getters + setters
}
Advert class:
public class Advert {
private Long id;
private String text;
private Account account;
// getters + setters
}
AccountMapper:
public class AccountMapper implements RowMapper<Account> {
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getLong("id"));
account.setUsername(rs.getString("username"));
return account;
}
}
Now, I am trying to create a Mapper for the Advert class. How can I map the account variable from the Advert class to a row? Many thanks
You can use Hibernate without affecting your application performance, just check out this Hibernate tutorial for hundreds of examples related too mapping entities.
As for doing that in JDBC, you need to doo the following steps:
You need to use aliases to all selected columns so that the ids columns won't clash.
You can define two row mappers and use a join from Advert to Account and pass it to the AccountMapper:
public class AdvertMapper implements RowMapper<Advert> {
public Advert mapRow(ResultSet rs, int rowNum) throws SQLException {
Advert advert = new Advert();
advert.setId(rs.getLong("advert_id"));
advert.setText(rs.getString("advert_text"));
return advert;
}
}
public class AccountMapper implements RowMapper<Account> {
private final AdvertMapper advertMapper;
public AccountMapper(AdvertMapper advertMapper) {
this.advertMapper = advertMapper;
}
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getLong("account_id"));
account.setUsername(rs.getString("account_username"));
Advert advert = this.advertMapper.mapRow(rs, rowNum);
advert.setAccount(account);
account.getAdverts().add(advert);
return account;
}
}
The AccountMapper uses the AdvertMapper to create Adverts from the joined data.
Compare this to Hibernate, where all these mappings are resolved for you.
Well if you do not use an ORM ... you have no object relation mapping ! After all the ORMs were created for that reason :-)
More seriously, ORM saves you from writing a lot of boilerplate code. Using direct JDBC instead of JPA is a code optimisation. Like any other code optimisation, it should be used when appropriate. It is relevant for :
libraries using few tables that do not want to rely on an ORM (ex: user, roles, and ACL in spring security)
identified bottlenecks in larger application
My advice should be to first use JPA or native hibernate hidden in a DAO layer. Then carefully analyze your performance problems and rewrite the most expensive parts in JDBC.
Of course, you can directly code you DAO implementations in JDBC, but it will be much longer to write.
I almost forgot the essential part : in an ORM you map classes and relations, in JDBC you write independant SQL queries.
Solving the one to one case is easy with as Vlad answered, If you want to map a one to many as your Account - Advert suggest you can't do that
with a RowMapper because you will try to map multiple rows of your ResultSet to one Account, many Advert.
You can also do that manually or you can also use http://simpleflatmapper.org that provides mapping from ResultSet to POJO with one to many support.
Beware that the bidirectional relationship is not great there if you really want those it's possible but they won't be the same instance.
Checkout
http://simpleflatmapper.org/0104-getting-started-springjdbc.html
and
https://arnaudroger.github.io/blog/2017/02/27/jooq-one-to-many.html
you will need to get a ResutlSetExtractor - it's thread safe so only need one instance -,
private final ResultSetExtractor<List<Account>> mapper =
JdbcTemplateMapperFactory
.newInstance()
.addKeys("id") // assuming the account id will be on that column
.newResultSetExtractor(Account.class);
// in the method
String query =
"SELECT ac.id as id, ac.username, ad.id as adverts_id, ad.text as adverts_text"
+ "FROM account ac LEFT OUTER JOIN advert ad ON ad.account_id = ac.id order by id "
// the order by id is important here as it uses the break on id on the root object
// to detect new root object creation
List<Account> results = template.query(query, mapper);
with that you should get a list of account with the list of adverts populated. but advert won't have the account.

JPA: Fetch data from DB instead of Persistance Context

I have a simple User Account application in which the user is able to change his details.
Updating the Database
The Managed Bean's method which takes the form parameters and calls the Service method:
public String changeDetails(){
Date date = DateUtil.getDate(birthDate);
Integer id = getAuthUser().getId();
UserDetail newDetails = new UserDetail(id, occupation, date, originCity, residenceCity, description);
EntityTransaction transaction = getTransaction();
userService.updateDetail(newDetails);
transaction.commit();
return null;
}
The Service Method:
public boolean updateDetail(UserDetail newDetails) {
boolean ok = true;
if (newDetails != null) {
UserDetail user = readDetail(newDetails.getId());
user.setOccupation(newDetails.getOccupation());
user.setOriginCity(newDetails.getOriginCity());
user.setResidenceCity(newDetails.getResidenceCity());
user.setBirth(newDetails.getBirth());
user.setDescription(newDetails.getDescription());
}
return ok;
}
Fetching data from DB
#PostConstruct
public void init(){
userService = new UserService();
sessionController.setAuthUser(userService.read(getAuthUser().getId()));
originCity = getAuthUser().getUserDetail().getOriginCity();
residenceCity = getAuthUser().getUserDetail().getResidenceCity();
occupation = getAuthUser().getUserDetail().getOccupation();
birthDate = DateUtil.getStringDate(getAuthUser().getUserDetail().getBirth());
description = getAuthUser().getUserDetail().getDescription();
}
The problem is that the behavior of this code is different. Sometimes I obtain the desired result: once I submit the new details and call the #PostConstruct init () the new details are printed. Some other times the old details are printed even though the DB entry is updated.
Conclusion: Sometimes the JPA brings me different result from what is in the DB. I guess that this results consist of data from the Persistance Context, data which isn't updated. Is there a way in which I can be sure that the JPA always brings the data directly from the DB? Or is there something I'm missing?
If you are using JPA 2 then #Cacheable(false) on your entity definition should make it read from the DB every time.
You mean is there a way to turn the cache off or empty it before an operation ?
emf.getCache().evictAll();

Categories

Resources