Before going to actual issue let me brief what i am looking for.
I am looking for encrypt and decrypt the fields inside entity. in JPA, we can use Attribute converter and achieve this. but in spring data jdbc its not supported it seems.
So, i am trying to use customconverstions feature of spring data jdbc. here i am creating one type like below
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
public class EncryptionDataType {
private String value;
#Override public String toString() {
return value ;
}
}
in Pojo i will use this type as field.
#Table("EMPLOYEE")
#Builder
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(callSuper = true)
#Data
public class Employee
{
#Column("name")
private EncryptionDataType name;
#Id
private Integer id;
#Version
private Long version;
}
so from this i am expecting to save the 'EncryptionDataType' as normal string column in mysql for this i have created converter for read and write
#WritingConverter
public class EncryptionDataTypeWriteConverter implements Converter<EncryptionDataType, String> {
#Override public String convert(EncryptionDataType source) {
return source.toString()+"add";
}
}
#ReadingConverter
public class EncryptionDataTypeReadConverter implements Converter<String, EncryptionDataType> {
#Override public EncryptionDataType convert(String source) {
return new EncryptionDataType(source);
}
}
configuring these converts in configuration file.
#Configuration
public class MyConfig {
#Bean
protected JdbcCustomConversions JdbcConversion(Dialect dialect) {
return new JdbcCustomConversions(
Arrays.asList(new EncryptionDataTypeReadConverter(),new EncryptionDataTypeWriteConverter()));
}
}
This configurations seems not working. i am getting below error.
PreparedStatementCallback; bad SQL grammar [INSERT INTO `encryption_data_type` (`name`, `value`) VALUES (?, ?)]; nested exception is java.sql.SQLSyntaxErrorException: Table 'testschema.encryption_data_type' doesn't exist
seems instead of converting my encryptionDataType to string its trying to insert into new table. Please help me. am i missing anything ?
Updated configuration code:
#Configuration
#EnableJdbcRepositories(transactionManagerRef = "CustomJdbcTranasactionManager", jdbcOperationsRef = "CustomJdbcOperationsReference", repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class, basePackages = {
"com.java.testy"
})
#EnableAutoConfiguration(exclude = {
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class,
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration.class
})
public class MyConfig {
#Bean
protected JdbcCustomConversions JdbcConversion(Dialect dialect) {
return new JdbcCustomConversions(
Arrays.asList(new EncryptionDataTypeReadConverter(),new EncryptionDataTypeWriteConverter()));
}
// creating beans for datasource,JdbcOperationsReference,JdbcTranasactionManager,JdbcConverter,JdbcMappingContext,DataAccessStrategy,JdbcAggregateTemplate
}
Make your configuration extend AbstractJdcbcConfiguration and overwrite jdbcConfiguration().
Just updating Jens Schauder's answer (I think it's just a typo - I would comment but don't have the rep):
Make your configuration extend AbstractJdcbcConfiguration and overwrite jdbcCustomConversions() (or possibly userConverters(), if that suits the purpose).
Related
I am trying to find a way I can implement the repository pattern using spring boot with Generic types. So far I looked into this article:
https://thoughts-on-java.org/implementing-the-repository-pattern-with-jpa-and-hibernate/
and tried implementing this solution using generic types based on the solution to this question:
Using generics and jpa EntityManager methods
I attempted to do so using JPA and Hibernate but for me, an error appears when I try returning the class of the entity on the specified type parameter.
the following is my User model using JPA and Hibernate:
package models;
import javax.persistence.*;
#Entity
public class User extends Model {
#Id
#Column(name = "id", updatable = false, nullable = false)
private String id;
public String username;
private String password;
public User(String username, String password) {
super();
this.username = username;
this.password = password;
}
public String getPassword() {
return password;
}
}
The following is my interface for basic CRUD operations:
package repositories;
import models.Model;
import java.util.UUID;
public interface IRepository<T> {
void add(T entity);
void delete(String id);
void update(T entity);
T get(String id);
boolean exists(String id);
}
I then created an abstract class for all repositories to avoid repeating myself for all Models.
package repositories;
import models.Model;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
public abstract class Repository<T> implements IRepository<T>{
private EntityManager em;
public Repository(EntityManager em){
this.em = em;
}
#Override
public void add(T entity) {
em.persist(entity);
}
#Override
public void delete(String id) {
T entity = get(id);
em.remove(entity);
}
#Override
public void update(T entity) {
em.merge(entity);
}
#Override
public T get(String id) {
return em.find(getEntityClass(), id);
}
public boolean exists(String id) {
return em.contains(get(id));
}
// link to an explanation can be found at:https://stackoverflow.com/questions/40635734/using-generics-and-jpa-entitymanager-methods
// when a class extends this class, all is needed is to fill out the method body of to return the class.
public abstract Class<T> getEntityClass();
}
the abstract class is there for me to return the class that belongs to T
and this is the specific repository for Users:
package repositories;
import models.Model;
import models.User;
import javax.persistence.EntityManager;
public class UserRepository<User> extends Repository<User> {
public UserRepository(EntityManager em) {
super(em);
}
#Override
public Class<User> getEntityClass() {
return null;
}
}
Ideally, for the getEntityClass method, I would like to return User.class, but I get an error on the IDE saying "Cannot select from type variable". I have looked at a few more questions online and another thing people tried was either put a parameter of type Class or have a member of type Class within the User repository. I tried both methods and it didn't work, any ideas?
class UserRepository<User> should just be class UserRepository. Otherwise, User is just like T, a generic type. Not the class User.
But you're reinventing the wheel. Learn and use Spring Data JPA, which brings generic repositories, and more.
I have two classes. RequestDTO and Entity. I want to map RequestDTO to the Entity. In that case, I want to insert one of the Entity property manually which means that property is not in the Request DTO. How to achieve this using modelmapper.
public class RequestDTO {
private String priceType;
private String batchType;
}
public class Entity {
private long id;
private String priceType;
private String batchType;
}
Entity newEntity = modelMapper.map(requestDto, Entity.class);
But this does not work, it says it can't convert string to long. I request a solution to this or a better approach to this.
If you want to perform the mapping manually (ideally for dissimilar objects)
You can check the documentation for dissimilar object mapping Property Mapping,
You can define a property mapping by using method references to match
a source getter and destination setter.
typeMap.addMapping(Source::getFirstName, Destination::setName);
The source and destination types do not need to match.
typeMap.addMapping(Source::getAge, Destination::setAgeString);
If you don't want to do the mapping field by field to avoid boilerplate code
you can configure a skip mapper, to avoid mapping certain fields to your destination model:
modelMapper.addMappings(mapper -> mapper.skip(Entity::setId));
I've created a test for your case and the mapping works for both side without configuring anything :
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Before;
import org.junit.Test;
import org.modelmapper.ModelMapper;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
public class ModelMapperTest {
private ModelMapper modelMapper;
#Before
public void beforeTest() {
this.modelMapper = new ModelMapper();
}
#Test
public void fromSourceToDestination() {
Source source = new Source(1L, "Hello");
Destination destination = modelMapper.map(source, Destination.class);
assertNotNull(destination);
assertEquals("Hello", destination.getName());
}
#Test
public void fromDestinationToSource() {
Destination destination = new Destination("olleH");
Source source = modelMapper.map(destination, Source.class);
assertNotNull(source);
assertEquals("olleH", destination.getName());
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
class Source {
private Long id;
private String name;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
class Destination {
private String name;
}
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.
My springboot app tries to read data from two datasources(emwbis and backupemwbis). I've followed the below link in configuring my springboot app to read data from two different datasources.
http://www.baeldung.com/spring-data-jpa-multiple-databases
The current problem with my app is it is always reading data from the primary datasource(emwbis). I've written below code.
Model classes for primary and backup datasources:
package com.jl.models.primary;
#Entity
#Table(name = "crsbis",schema="emwbis")
#Data
public class CrsBIS {
#Id
private String id;
#NotNull
private String email;
package com.jl.models.backup;
import lombok.Data;
#Entity
#Table(name = "crsbis",schema="backupemwbis")
#Data
public class CrsBIS {
#Id
private String id;
#NotNull
private String email;
Datasource config classes for primary and backup datasources:
#Configuration
#PropertySource("classpath:persistence-multiple-db.properties")
#EnableJpaRepositories(basePackages = "com.jl.dao.backup", entityManagerFactoryRef = "crsBISBackUpEntityManager", transactionManagerRef = "crsBISBackupTransactionManager")
public class BackupCrsBISDatabaseConfig {
#Configuration
#PropertySource("classpath:persistence-multiple-db.properties")
#EnableJpaRepositories(basePackages = "com.jl.dao.primary", entityManagerFactoryRef = "crsBISEntityManager", transactionManagerRef = "crsBISTransactionManager")
public class CrsBISDatabaseConfig {
Repository interfaces for primary and backup datasources:
#Transactional
public interface CrsBISRepository extends JpaRepository<CrsBIS, String> {
public CrsBIS findById(String id);
}
#Transactional
public interface CrBisBackupRepository extends JpaRepository<CrsBIS, String>{
public CrsBIS findById(String id);
}
Persistent db proeprties file :
jdbc.driverClassName=com.mysql.jdbc.Driver
crsbis.jdbc.url=jdbc:mysql://localhost:3306/emwbis
backupcrsbis.jdbc.url=jdbc:mysql://localhost:3306/backupemwbis
jdbc.user=root
jdbc.pass=Password1
Controller class to test both the datasources :
#Controller
public class CrsBISController {
#Autowired
private CrsBISRepository crsBISRepository;
#Autowired
private CrBisBackupRepository crsBackupRepository;
#RequestMapping("/get-by-id")
#ResponseBody
public String getById(String id){
String email="";
try{
CrsBIS crsBIS = crsBISRepository.findById(id);
email = String.valueOf(crsBIS.getEmail());
}catch (Exception e) {
e.printStackTrace();
return "id not found!";
}
return "The email is : "+email;
}
#RequestMapping("/get-by-id-backup")
#ResponseBody
public String getByIdFromBackup(String id){
String email="";
try{
com.jl.models.backup.CrsBIS crsBIS = crsBackupRepository.findById(id);
email = String.valueOf(crsBIS.getEmail());
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return "id not found!";
}
return "The email is : "+email;
}
Although, I've separated the database schemas in the model classes and in the database config file, both the methods in the controller class hit the same database (emwbis). I want getByIdFromBackup method in controller class to read the data from secondary database (backupemwbis).
Can someone please let me know the mistake in my code? Or you can suggest/guide me to achieve my goal?
From the first configuration file you're creating a primary datasource bean definition with the name myDatasource and in the second emf you're injecting the same datasource reference.
The Bean causing the problem is this
#Bean
#Primary
public DataSource myDataSource()
Just change the second Bean datasource name and use it in the second EMF.
public class BackupCrsBISDatabaseConfig {
...
#Bean
public DataSource backupDS() {
....
#Bean
public LocalContainerEntityManagerFactoryBean crsBISBackUpEntityManager() {
....
em.setDataSource(backupDS());
}
}
Hope this fixes it.
You have to explicitly request a TransactionManager implementation in your #Transactional usage:
#Transactional("crsBISTransactionManager")
//..
#Transactional("crsBISBackupTransactionManager")
//..
I have one global naming strategy but for a few entities I want to use a different one. Is it possible in jpa or hibernate?
clarification: i don't want to use #Table(name="xxx") nor #Column(name="xxx"). i'm asking about naming strategy component (described for example here: Hibernate naming strategy). that's a component that infer the column and table names for you
I don't see a way in the Hibernate source code. The EntityBinder is coming up with names using ObjectNameNormalizer.NamingStrategyHelper, which gets the naming strategy from either Configuration.namingStrategy (the global one) or from a complex path which goes through MetadataImpl and lands nowhere (no usages).
So you're likely stuck with overriding field names manually. I don't even see an obvious way to get context about the field, so I think even a split-brain naming strategy looks like it's out of the question.
Update: After seeing #anthony-accioly's answer, I thought I that last sentence may have been wrong. So I tested it as follows
package internal.sandbox.domain;
#Entity
public class SomeEntity {
private String id;
private String someField;
#Id
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSomeField() {
return someField;
}
public void setSomeField(String someField) {
this.someField = someField;
}
}
with a JpaConfiguration as follows
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories("internal.sandbox.dao")
#Import(DataSourceConfiguration.class)
public class JpaConfiguration {
#Bean
#Autowired
public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean(DataSource dataSource) {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.PostgreSQL82Dialect");
vendorAdapter.setDatabase(Database.POSTGRESQL);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("internal.sandbox"); // note, no ".domain"
factory.setDataSource(dataSource);
Properties properties = new Properties();
properties.setProperty("hibernate.cache.use_second_level_cache", "false");
properties.setProperty("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");
factory.setJpaProperties(properties);
return factory;
}
...
a Spring Data DAO as follows
public interface SomeEntityDao extends CrudRepository<SomeEntity, String> {
}
and an integration test as follows
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ApplicationConfiguration.class, JpaConfiguration.class})
public class SomeEntityDaoIntegrationTests {
#Autowired
private SomeEntityDao someEntityDao;
#Test
public void testSave() {
SomeEntity someEntity = new SomeEntity();
someEntity.setId("foo");
someEntity.setSomeField("bar");
this.someEntityDao.save(someEntity);
}
}
I put breakpoints in the ImprovedNamingStrategy, and classToTableName() was called with "SomeEntity" and propertyToColumnName() was called with "someField".
In other words, package information isn't being passed in, so at least in this setup, it can't be used to apply a different naming strategy based on package name.