I have an API component class and each of its business methods need different repositories. This is what I have for example:
#Component
#Transactional (propagation = Propagation.SUPPORTS,
rollbackForClassName="java.lang.Throwable")
#EnableTransactionManagement
public class TransactionApi
{
#Autowired
private TransactionRepository repo;
#Autowired
private ProductRepository prodRepo;
#Autowired
private CustomerRepository custRepo;
#Autowired
private OrderRepository orderRepo;
#Autowired
private ContactRepository contactRepo;
#Autowired
private ShippingPrefsRepository shipPrefsRepo;
#Transactional (readOnly=true)
public Transaction getTransactionDetails(String transactionId)
{
return repo.findOne(transactionId);
}
#Transactional (readOnly=true)
public Product getProductDetails(String productId)
{
return prodRepo.findOne(productId);
}
#Transactional (readOnly=true)
public Customer getCustomerDetails(String customerId)
{
return custRepo.findOne(customerId);
}
public Order createOrder(Order order)
{
Order saved = orderRepo.save(order);
return saved;
}
public Contact createContact(Contact contact)
{
Contact saved = contactRepo.save(contact);
return saved;
}
public ShippingPreference createContact(ShippingPreference shipPref)
{
ShippingPreference saved = shipPrefsRepo.save(shipPref);
return saved;
}
Looking at above design, i have a lot of repositories used in one class and so not all are used each time this class is used. So i think initializing all of them upfront would be an overkill (I assume). I am worried that this might slow down my application or use lot of resources (memory). I will probably will have another 20 repositories when I am done with this class.
My question is am I designing it correctly as it should be done? Or is there better way of doing it. Loading too many repositories into memory when each time just one of those are being used, isn't it inefficient way of doing it? Any suggestions/advice are appreciated.
Related
I observed that the .save() method executes an extra SELECT query to check whether the corresponding record already exists when the corresponding ID is not a AUTO INCREMENT one.
I tried to implement a repository for this kind of situation that will be extended by many JpaRepository interfaces which will be used across different stateless services and I would like to know if my code is safe - race conditions wise - accross multiple requests as I am not that comfortable using the EntityManager yet.
User Entity :
public class User {
#Id
#Column(name = "username", nullable = false, length = 45)
private String username;
#Column(name = "password", nullable = false, length = 256)
private String password;
}
Solution 1 :
public interface SimpleRepository<T> {
void persist(T entity);
}
public class SimpleRepositoryImpl<T> implements SimpleRepository<T> {
#PersistenceContext
EntityManager entityManager;
#Transactional
#Override
public void persist(T entity) {
entityManager.persist(entity);
}
}
User Repository :
public interface UserRepository extends JpaRepository<User, String>, SimpleRepository<User> {}
User Service :
#Service
#AllArgsConstructor
public class UserService {
private final UserRepository userRepository;
public void createUser(User user) {
this.userRepository.persist(user);
}
}
The same implementation will be followed across many different JPA Repositories and Services in the application.
If the solution above is not safe how about this one?
#Service
public class PersistenceService {
#PersistenceContext
private EntityManager entityManager;
#Transactional
public <T> void persist(T entity) {
entityManager.persist(entity);
}
}
Which will turn my UserService and every other Service that is in need of the same functionality to :
#Service
#AllArgsConstructor
public class UserService {
private final PersistenceService persistenceService;
public void createUser(User user) {
this.persistenceService.persist(user);
}
}
I am a newbie at Spring Boot framework.
I am trying to develop a server which responds to clients' requests providing json files representing book models in my mongodb repository through a REST API architecture.
This is what I have:
Book
public class Book implements Serializable {
#Id
private String id;
private String name;
private String author;
public Book(String id, String name, String author) {
this.id = id;
this.name = name;
this.author = author;
}
// Getters and setters here
}
BookController
#RestController
#RequestMapping(path = "api/v1/books")
public class BookController {
private final BookService bookService;
#Autowired
public BookController(BookService bookService) {
this.bookService = bookService;
}
#GetMapping
public List<Book> getBooks() {
return bookService.getBooks();
}
#GetMapping(params = "author")
public List<Book> getBooksByAuthor(#RequestParam String author) {
return bookService.getBooksByAuthor(author);
}
}
BookService
#Service
public class BookService {
#Autowired // warning: field injection is not recommended
private BookRepository bookRepository;
#Autowired // warning: field injection is not recommended
private MongoTemplate mongoTemplate; // warning: could not autowire. No 'MongoTemplate' type found
public List<Book> getBooks() {
return bookRepository.findAll();
}
public List<Book> getBooksByAuthor(String author) {
Query query = new Query();
query.addCriteria(Criteria.where("author").is(author));
return mongoTemplate.find(query, Book.class);
}
}
BookRepository
#Repository
public interface BookRepository extends MongoRepository<Book, String> {
}
Even though localhost:8080/api/v1/books?author=SOMETHING works properly, IntelliJ warns me that #Autowired private BookRepository bookRepository;
field injection is not recommended
and #Autowired private MongoTemplate mongoTemplate;
field injection is not recommended
could not autowire. No 'MongoTemplate' type found
I have these questions:
Is the structure of this server application correct? I mean, the interaction between each components (controller, service and repository)
How can I overcome these two warnings in BookService?
Is there any change you would apply to my code? For example naming conventions, autowiring and so on? (Some advices, I mean). For example I would move
Query query = new Query();
query.addCriteria(Criteria.where("author").is(author));
return mongoTemplate.find(query, Book.class);
statements as a BookRepository
Filed injection is not recommended, to avoid this kind of warnings you should give priority to constructor injections, which allows you to build immutable components.
Like this:
#Service
public class BookService {
private final BookRepository bookRepository;
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
public List<Book> getBooks() {
return bookRepository.findAll();
}
public List<Book> getBooksByAuthor(String author) {
return bookRepository.findAllByAuthor(author);
}
}
About the MongoTemplate - why don't you create a method 'findAllByAuthor' inside your BookRepository interface instead of using MongoTemplate? This will make your code more general and will incapsulate query logic into repository class where it should be incapsulated.
Currently, we're looking for a solution to save the following User entity into multiple MongoDB collections at the same time (i.e. db_users and on db_users_legacy). Both collections are in the same database.
Please don't ask me the reason why I need to save in two collections. It is a business requirement.
#Document(collection = "db_users")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
private String id;
private String name;
private String website;
private String name;
private String email;
}
And my SpringBoot application configuration goes as;
#Configuration
public class ApplicationConfig {
#Bean
public MongoTemplate mongoTemplate(MongoDbFactory factory){
MongoTemplate template = new MongoTemplate(factory);
template.setWriteConcern(WriteConcern.ACKNOWLEDGED);
retu,rn template;
}
}
Currently my repository looks as this. And saving works perfectly fine. How can I same this document in two different collections?
#Repository
public class UserRepositoryImpl implements UserRepository {
private MongoTemplate mongoTemplate;
public UserRepositoryImpl(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#Override
public void save(User user) {
mongoTemplate.save(user);
}
}
Can anyone suggest the best option to deal with this, please?
I suggest using MongoTemplate's the other overloaded save method.
#Override
public void save(User user) {
mongoTemplate.save(user, "db_users");
mongoTemplate.save(user, "db_users_legacy");
}
This can be used to save same object to multiple collections.
From docs,
You can customize this by providing a different collection name using the #Document annotation. You can also override the collection name by providing your own collection name as the last parameter for the selected MongoTemplate method calls.
So it doesn't matter the collection name specifically provided in #Document, you can always override it using MongoTemplate.
I have created a repository but when I call my repository it gives a NullPointerException everytime. Can someone help me figure out why?
My repository
#Repository
public interface WorkflowObjectRepository extends CrudRepository<WorkflowObject, String> {
#Override
WorkflowObject findOne(String id);
#Override
void delete(WorkflowObject workflowObject);
#Override
void delete(String id);
#Override
WorkflowObject save(WorkflowObject workflowObject);
}
My Object
#Data
#Entity
#Table(name = "workflowobject")
public class WorkflowObject {
#GeneratedValue
#Column(name="id")
private String id;
#Column(name = "state_name")
private String stateName;
}
My test
public class Test {
#Autowired
static WorkflowObjectRepository subject;
public static void main(String[] args) {
final WorkflowObject obj = new WorkflowObject();
obj.setId("maha");
obj.setStateName("test");
subject.findOne("maha");
}
}
application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/vtr?
autoReconnect=true
spring.datasource.username=vtr
spring.datasource.password=vtr
The problem is you are trying to autowire a static data member
#Autowired
static WorkflowObjectRepository subject;
What happens in your case is static is getting initialized before the bean so you are autowiring on null, just remove the static and deal with it as instance variable.
repositories are singletones so no point of making them static
In order to work properly with #Autowired you need to keep in mind that spring scans annotations to allow automatically classes load.
If you need to #Autowired some class, the class Test needs to have some annotation, that tells to Spring Boot to find it.
Your Test class need to have #Component, #Service, #Controller, #Repository, etc. Otherwise #Autowired will not work because Spring Boot not know your class and will not process it.
You can find some explanation here
In my webapp, Spring transaction and Hibernate session API are used.
Please see my service and DAO classes and usage below;
BizCustomerService
#Service
#Transactional(propagation = Propagation.REQUIRED)
public class BizCustomerService {
#Autowired
CustomerService customerService;
public void createCustomer(Customer cus) {
//some business logic codes here
customerService.createCustomer(cus);
//***the problem is here, changing the state of 'cus' object
//it is necessary code according to business logic
if (<some-check-meet>)
cus.setWebAccount(new WebAccount("something", "something"));
}
}
CustomerService
#Service
#Transactional(propagation = Propagation.REQUIRED)
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CustomerService {
#Autowired
CustomerDAO customerDao;
public Long createCustomer(Customer cus) {
//some code goes here
customerDao.save();
}
}
CustomerDAO
#Repository
public class CustomerDAO {
#Autowired
private SessionFactory sessionFactory;
private Session getSession() {
return sessionFactory.getCurrentSession();
}
public Long save(Customer customer) {
//* the old code
//return (Long) getSession().save(customer);
//[START] new code to change
Long id = (Long) getSession().save(customer);
//1. here using 'customer' object need to do other DB insert/update table functions
//2. if those operation are failed or success, as usual, they are under a transaction boundary
//3. lets say example private method
doSomeInsertUpdate(customer);
//[END] new code to change
return id;
}
//do other insert/update operations
private void doSomeInsertUpdate(customer) {
//when check webAccount, it is NULL
if (customer.getWebAccount() != null) {
//to do something
}
}
}
Customer
#Entity
#Table(name = "CUSTOMER")
public class Customer {
//other relationships and fields
#OneToOne(fetch = FetchType.LAZY, mappedBy = "customer")
#Cascade({CascadeType.ALL})
public WebAccount getWebAccount() {
return this.webAccount;
}
}
In the above code, customer is created in BizCustomerService then may change the state of related WebAccount after persisted through DAO. And when the transaction is committed, a new Customer and related WebAccount object are persisted to DB. This is normal I know.
The problem is; in CustomerDAO#save() >> doSomeInsertUpdate() method, 'webAccount' is NULL and the value is not set yet at that time.
EDIT : Left to mention that, it is restricted and don't want to change the code at BizCustomerService and CustomerService because there can be many invocations to DAO methods it can impact alot. So want to change only at DAO level.
So my question is how can I access WebAccount object in doSomeInsertUpdate() method? Any Hibernate usage needed?
Thanks in advance!!
not sure what you're expecting, but I think there is no magic here. If you want a WebAccount to be != null, you'll have to explicitly make one and save it to the DB.
WebAccount wb = new WebAccount();
getSession().save(wb);
customer.setWebAccount(wb);