Spring DATA for RDBMS and NoSql - database

Is it possible to make an application using Spring DATA with common code that supports both RDMS and Nosql(MongoDb) as back-end data store.It should support either one of them at one point of time and it should be configurable.

I have just pushed a new Spring-Data project named spring-data-gremlin which aims to do exactly this. It uses JPA annotations to map to any Tinkerpop blueprints graph database (OrientDB, TitanDB, etc). This means that switching between RDBMS and nosql graph databases should be a matter of configuration for any Spring-Data-JPA project.
Note: The project is in early stages of development and therefore not all JPA annotations are implemented yet.

I don't know for sure for MongoDB but we currently have projects configured with Spring Data JPA and Spring Data Neo4J simultaneously. I can't think of any obstacles why you could not make this work with Spring Data JPA and Spring Data MongoDB.
Be aware of transaction management: as far as I know MongoDB does not support transactionability so any kind of writing to both data sources can not be done as atom operation. If this is not an issue, you're good to go.
Our example snippet:
<neo4j:config storeDirectory="${neo4j.storeDirectory}"
base-package="app.model.neo4j" />
<neo4j:repositories base-package="app.neo4j.repo" />
<tx:annotation-driven transaction-manager="neo4jTransactionManager" />
And Spring Data JPA in Configuration annotated class:
#Configuration
#EnableJpaRepositories(value = "app.dao", entityManagerFactoryRef = "emf", transactionManagerRef = "tm")
#ComponentScan("app")
#EnableTransactionManagement
public class ConfigDao {
protected final String PROPERTY_DB_MODEL_PACKAGESTOSCAN = "db.model.packagesToScan";
protected final String PROPERTY_DB_DRIVER_CLASSNAME = "db.driver.className";
protected final String PROPERTY_DB_URL = "db.url";
protected final String PROPERTY_DB_USERNAME = "db.username";
protected final String PROPERTY_DB_PASSWORD = "db.password";
protected final String PROPERTY_DB_ADDITIONAL_DDL = "hibernate.hbm2ddl.auto";
protected final String PROPERTY_DB_ADDITIONAL_DIALECT = "hibernate.dialect";
protected final String PROPERTY_DB_ADDITIONAL_EMF_NAME = "hibernate.ejb.entitymanager_factory_name";
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(PROPERTY_DB_DRIVER_CLASSNAME);
dataSource.setUrl(PROPERTY_DB_URL);
dataSource.setUsername(PROPERTY_DB_USERNAME);
dataSource.setPassword(PROPERTY_DB_PASSWORD);
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
#Bean
public EntityManager entityManager() {
return entityManagerFactory().getObject().createEntityManager();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(PROPERTY_DB_MODEL_PACKAGESTOSCAN);
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalJpaProperties());
return em;
}
#Bean
protected Properties additionalJpaProperties() {
Properties properties = new Properties();
properties.setProperty(PROPERTY_DB_ADDITIONAL_DDL);
properties.setProperty(PROPERTY_DB_ADDITIONAL_DIALECT);
properties.setProperty(PROPERTY_DB_ADDITIONAL_EMF_NAME);
return properties;
}
}
Hope it helps.

Related

Dynamic NamedDatabase in Play Framework

I'm running a java play framework setup where I would like to have several databases depending on what customer is making the call. I have a jwt setup where there is a tenant id. However I can't get my head around what's best practise in Play regarding this. As for now I have this code:
public class JavaNamedDatabase {
private Database db;
private DatabaseExecutionContext executionContext;
private static final Logger.ALogger LOGGER = Logger.of(JavaNamedDatabase.class);
#Inject
public JavaNamedDatabase(
#NamedDatabase("xxx") Database db, DatabaseExecutionContext executionContext) {
this.db = db;
this.executionContext = executionContext;
}
where I would like to make "xxx" dynamic depending on which tenant is making the request.
Is it possible to pass this parameter or do I need to have separate classes?
Or maybe the best solution is just to have one instance running per customer and have the #NamedDatabase as a runtime config parameter?
I found DBApi where there is a getter for Database.
public class JavaNamedDatabase {
private DBApi dbApi;
private DatabaseExecutionContext executionContext;
private static final Logger.ALogger LOGGER = Logger.of(JavaNamedDatabase.class);
#Inject
public JavaNamedDatabase(
DBApi dbApi, DatabaseExecutionContext executionContext) {
this.dbApi = dbApi;
this.executionContext = executionContext;
}
public CompletionStage<Integer> addGenreToPlayItem(Integer playItemId, String genre) {
return CompletableFuture.supplyAsync(
() ->
dbApi.getDatabase("xxx").withConnection(...```

Spring Data MongoDB #Transactional failure

Could someone please tell me why this spring transaction is not rolling back appropriately?
The error I get is this:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available
This is my repository with a save transaction that will intentionally fail:
#Repository
public class TransactionalRepository {
private final PlayerRepository playerRepository;
#Autowired
public TransactionalRepository(PlayerRepository playerRepository) {
this.playerRepository = playerRepository;
}
public Player saveSuccess(Player player) {
return playerRepository.save(player);
}
#Transactional
public Player saveFail(Player player) {
player.setName("FAIL"); // should not be saved in DB if transaction rollback is successful
player = playerRepository.save(player);
throw new IllegalStateException("intentionally fail transaction");
}
}
And here is the test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MongoTransactionApplicationTests {
#Autowired
public TransactionalRepository playerRepository;
#Test
public void contextLoads() {
Player player = new Player();
player.setId(UUID.randomUUID().toString());
final String PLAYER_NAME = "new-"+player.getId().subSequence(0,8);
player.setName(PLAYER_NAME);
player = playerRepository.saveSuccess(player);
try {
player = playerRepository.saveFail(player);
} catch (IllegalStateException e) {
// this is supposed to fail
}
Assert.assertEquals(PLAYER_NAME, player.getName());
}
}
Download all the code here if you want to see it run
Unlike other implementations the Spring Data MongoDB module does not by default register a PlatformTransactionManager if none is present. This is up to the users configuration, to avoid errors with non MongoDB 4.x servers as well as projects already using #Transactional along with a non MongoDB specific transaction manager implementation. Please refer to the reference documentation for details.
Just add a MongoTransactionManager to your configuration.
#Bean
MongoTransactionManager txManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
You might also want to check out the Spring Data Examples and have a look at the one for MongoDB transactions.

Spring rest application and exception localization

I am developing a spring boot app with restful services and angularjs as front end. The application must support multiple languages. One of the things I am having problem is the business exceptions thrown from my services. E.g. I have a book service which may throw an exception like this
if (book == null) {
throw new ServiceException("The book you are looking for no longer exist");
}
What is the best approach to localize them?
You have to use #RestControllerAdvice to seprate your exception handling logic from your business code. As per #ControllerAdvice doc, the methods defined in the class annotated as #ControllerAdvice apply globally to all Controllers. #RestControllerAdvice is just a convenience class equal to (#RestControllerAdvice = #ControllerAdvice + #ResponseBody). Please check below class:
#RestControllerAdvice
public class GenericExceptionHandler {
#Autowired
private MessageSource messageSource;
#ExceptionHandler(ServiceException.class)
public ResponseEntity<ErrorResponse> handle(ServiceException.class e, Locale locale) {
String errorMessage = messageSource.getMessage(
"error.message", new Object[]{},locale);
ErrorResponse error = new ErrorResponse();
error.setErrorCode(HttpStatus.BAD_REQUEST.value());
error.setMessage(errorMessage);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
}
// other Custom Exception handlers
Your ErrorResponse is normal javabean as below:
public class ErrorResponse{
private int errorCode;
private String message;
//getter and setter
}
And you should have MessageSource configured in your configuration to read the locale specific error messages as below:
#Configuration
public class MessageConfig {
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasename("i18n/messages");
source.setUseCodeAsDefaultMessage(true);
return source;
}
}
I would like to suggest use of #ControllerAdvice and #ExceptionHandler.
Also you can use #RestControllerAdvice, find example here

net core 1 (dnx 4.5.1) with enterpriselibrary 6 - setting up the connection string

i ve big problems running enterprise library data access block with net core 1 (dnx 4.5.1)
How can i setup the default connection string for entlib
my appsettings.json
"ConnectionString": "Server=localhost\sqlexpress;Initial Catalog=blind;User Id=blind;Password=blind"
Here is my problem (no default connectionstring)
Database db = DatabaseFactory.CreateDatabase();
how can i pass the appsettings ConnectionString to the entlib databasefactory
any help would be greatly appreciated
I know it's an old question, but I have a similar setup (but using .NET Core 2.0) and it took me awhile to figure out how to set the default database connection without using the web.config to manage it.
What I did was include the default database and all of the connection strings in the appsettings.json and then in my Startup class I read the appsettings.json into an object that I defined to store the default db name and the connection strings and configure the default + named database using DatabaseFactory.SetDatabase.
DatabaseFactory.SetDatabases() Definition
public class DataConfiguration
{
public string DefaultDatabase { get; set; }
public List<ConnectionStringSettings> ConnectionStrings { get; set; }
}
public class Startup
{
public Startup(IConfiguration configuration)
{
//Get the Database Connections from appsettings.json
DataConfig = configuration.Get<DataConfiguration>();
var defaultDb = DataConfig.ConnectionStrings?.Find(c => c.Name == DataConfig.DefaultDatabase);
DatabaseFactory.SetDatabases(() => new SqlDatabase(defaultDb.ConnectionString), GetDatabase);
Configuration = configuration;
}
public Database GetDatabase(string name)
{
var dbInfo = DataConfig.ConnectionStrings.Find(c => c.Name == name);
if (dbInfo.ProviderName == "System.Data.SqlClient")
{
return new SqlDatabase(dbInfo.ConnectionString);
}
return new MySqlDatabase(dbInfo.ConnectionString);
}
}
Whenever there is documentation, I always suggest reading it as it is usually good. This is one of those examples, check out the "Getting Started with ASP.NET 5 and Entity Framework 6". There are several things that you need to do to ensure that you are correctly configured.
Setup your connection string and DI.
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
}
Also, notice the path in the configuration, it seems to differ from yours.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped((_) =>
new ApplicationDbContext(
Configuration["Data:DefaultConnection:ConnectionString"]));
// Configure remaining services
}

Output data from database in JSF page

I am making a project using JSF, and I know how to get data from my view. I also know how to get data with the JDBC connector. And also how to put data in the view, from some objects, but my question is:
How to put data directly from my database, for example a list of person, in JSF, for example with the tag <h:outputText value="#{}"/> ?
I have found some examples with instantiate objects, but I did not found a real example with data from a DB.
JSF is just an MVC framework to develop web applications in Java. JSF doesn't associate with any data source at all. The only data JSF will use is retrieved from:
The data already stored in the proper object as attribute: HttpServletRequest, HttpSession or ServletContext.
The request/view/session/application context in form of fields in the managed beans, recognized by classes decorated as #ManagedBeans or #Named if using CDI. The data of these fields will be stored as attributes in the objects mentioned in the section above, depending on the scope of the managed bean.
By knowing this, then the only thing you should worry about is to fill the fields in your managed beans. You can fill them with incoming data from database, from a web service or whatever data source you have in mind.
For example, if you want/need to populate your data to pre process a request, you can do the following:
#ManagedBean
#ViewScoped
public class SomeBean {
List<Entity> entityList;
#PostConstruct
public void init() {
SomeService someService = new SomeService();
entityList = someService.findEntityList();
}
//getters and setters for the list...
}
//as you can see, this class is just pure Java
//you may use other frameworks if you want/need
public class SomeService {
public List<Entity> findEntityList() {
String sql = "SELECT field1, field2... FROM table";
List<Entity> entityList = new ArrayList<>();
try (Connection con = ...; //retrieve your connection somehow
PreparedStatement pstmt = con.prepareStatement(sql)) {
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
Entity entity = new Entity();
entity.setField1(rs.getString("field1"));
entity.setField2(rs.getString("field2"));
//...
entityList.add(entity);
}
} catch (Exception e) {
//handle exception ...
e.printStackTrace();
}
return entityList;
}
}

Resources