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(...```
Related
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.
In my task (dotnet core, c#), it is necessary to select one of the databases and make some kind of manipulation in accordance with the query.
According with microsoft docs, it's look like:
public class db1 : DbContext, Idb
{
public db1(DbContextOptions options) : base(options)
{}
}
public class db2 : DbContext, Idb
{
public db2(DbContextOptions options) : base(options)
{}
}
In Startup.cs
services.AddDbContext<db1>(options =>
options.UseSqlServer(Configuration.GetConnectionString("db1")));
services.AddDbContext<db2>(options =>
options.UseSqlServer(Configuration.GetConnectionString("db2")));
This allows you to register in DI and access a specific database connection instance, but all databases are hardcoded. This is a poor code. How is it better to make registration in DI by id of a database and select from DI's service by this id in a controller?
It is not so bad because you can change the connection string according to your environment, having different versions of your appsetings.json (appsettings.dev.json, appsettings.release.json and so on and so forth)
On the other hand you coulduse these context in your controllers contructors, i.e
ctor 1:
public FirstController(db1 context)
ctor2:
public SecondController(db2 context)
maybe, alse, ctor3:
public ThirdController(db1 contextA, db2 contextB )
BUT:
a) consider naming conventions (Idb?? db1??)
b) Why would you like to have Two same-kind-of repositories ... Oh! Are you trying to have a Generic repository pattern?? Then your answer is here: https://github.com/Arch/UnitOfWork (Im using it and I'm very hapy with the result and the performance, I'll paste an example bellow)
Using IUnitOfWork:
In your controller:
public YourController(IUnitOfWork unitOfWork)
{
try
{
_unitOfWork = unitOfWork;
// seeding
var ItemRepository = _unitOfWork.GetRepository<Item>();
//ETC...
In your StartUp, in ConfigureServices, call this method:
private void AddEntityFrameworkAndDbContext(IServiceCollection services)
{
services.AddEntityFrameworkSqlServer();
var migrationsAssemblyName = typeof(YourContext).GetTypeInfo().Assembly.GetName().Name;
services.AddDbContext<YourContext>(options =>
{
options.UseSqlServer(Your.ConnectionString.NoMAtterHowYouGetIt,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(migrationsAssemblyName);
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
}); // Man, this is for free, I spent days getting it to work
},
ServiceLifetime.Scoped // Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
).AddUnitOfWork<YourContext>();
}
And in Configure try something like:
app.EnsurePopulated(app.ApplicationServices.GetRequiredService());
I hope it helps you,
Juan
I have been looking all over the internet and there are answers for individual parts of this but I am looking for the best (most efficient) way to go about doing this.
I am creating an app for iPhone and I am currently designing a back end REST service. I know a lot of people will suggest a BaaS but the whole point of the app is because I wanted to create my own backend service. I am stuck at a part where I want to handle Push Notification History. I have already set up a user collection in MongoDB and a UserRepository.java to handle creating new users. However, there is nothing I can find about how to handle adding array entries of objects. (i.e. List)
Here is a screenshot taken from MongoDB Compass to show a sample user entry:
This is what I want to accomplish:
User.java
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection="user")
public class User {
#Id private String id;
private String firstName;
private String lastName;
private String userName;
private String password;
private String token;
private List<Notification> notificationList;
Notification.java
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection="notification")
public class Notification extends User {
#Id private String id;
private String title;
private String text;
private int badge;
private String sound;
private String payload;
UserRepository.java
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends MongoRepository<User, String> {
List<User> findByLastName(#Param("name") String name);
List<User> findByUserName(#Param("username") String name);
}
I am somewhat new to REST and Spring but want to know more about them. I am using RESTful Spring Boot in Spring Tools Suite. Thank you in advance for your help.
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.
Please forgive me as I am still very new to the world of test automation. I have started out by using Selenium WebDriver with JUnit4, predominately on windows OS, although I have modified my scripts and ran them on Mac.
I want to be able to create a set of classes containing set data such as usernames, passwords, default url . Perhaps even calling them from an excel file, but for now Im happy to store the data in classes and then pass that data into other test classes. Im guessing this would be a framework of some sort.
Currently I am writing classes that all begin with something like:
public class ExampleSQATest{
public static Chromedriver chrome;
#BeforeClass
public static void launchBrowser(){
System.setProperty("webdriver.chrome.driver", "chromedriver/chromedriver.exe");
chrome = new ChromeDrievr();
}
#Test
public void aLogin(){
chrome.manage().window().maximize();
chrome.navigate().to("http://mydummywebsite.com");
new WebDriverWait(chrome, 10).until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector
("input#UserName")));
WebElement username = chrome.findElementByCssSelector("input#UserName");
username.sendKeys("username");
WebElement password = chrome.findElementByCssSelector("input#Password");
password.sendKeys("password");
WebElement submit = chrome.findElementByCssSelector("input[type='submit']");
submit.click();
}
}
I will then proceed to write further test methods which requires entering data, but I'd like to be able to call this data from somewhere else that is already predefined.
Can anyone provide any suitable suggestions to investigate so I can learn. Something that is a guide or tutorial. Nothing too advanced, just something that helps me get started by advising me how to set a class of methods to be called by other classes and how it all links together as a framework.
Many thanks in advance.
One way to do this
public abstract class TestBase
{
private readonly INavigationManager navMgr;
private readonly IWindowNavigator windowNav;
private readonly ILoginManager loginMgr;
// All your stuff that is common for all the tests
protected TestBase()
{
this.navMgr = WebDriverManager.Get<INavigationManager>();
this.windowNav = WebDriverManager.Get<IWindowNavigator>();
this.loginMgr = WebDriverManager.Get<ILoginManager>();
}}
[TestFixture]
internal class QueriesTest : TestBase
{
private QueryTests queryTests;
[SetUp]
public void Setup()
{
this.queryTests = WebDriverManager.Get<QueryTests>();
// all the stuff you run specific before tests in this test class.
}
}
Assuming you have created test classes in webdriver-junit4, Use following two classes to call your test classes (Note-Import junit annotations)
1)Create test suite class as -
#RunWith(Suite.class)
#Suite.SuiteClasses({
YourTestClass1.class,
YourTestClass2.class,[you can add more tests you have created...]
})
public class TestSuiteJU {
}
2)Create class to call suite created above as-
public class TestExecution {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(TestSuiteJU .class);
}
}