Is there any way to specify a default schema in a properties file using r2dbc-mssql?
The connection works fine with:
spring:
r2dbc:
url: 'r2dbc:mssql://zzzzz.database.windows.net:1433/dbname'
username: 'xxxxxx'
password: 'xxxxxx'
but i have to use a static schema:
#Table("schemaname.foo")
public class Foo {
#Id
private Long id;
I've found something similar in r2dbc-postgresql:
https://github.com/pgjdbc/r2dbc-postgresql/issues/37
In a Spring Boot application, I think you can execute a sql statement to switch schemas in the #PostConstruct method of a #Configuration class.
#Configuration
class DatabaseConfig{
#Autowired
ConnectionFactory connectionFactory;
#PostConstruct
void init(){
Mono.from(connectionFactory.getConnection())
.flatMap(c->c.createStatement("switch to your schema here, different database has different commands here").execute())
.subscribe()
}
}
Related
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(...```
I've created a spring batch to query a Azure SQL server database and write the data into a CSV file. I do not have create permissions for the database. I get this error Invalid Object name BATCH_JOB_INSTANCE on running the batch. I don't want the spring batch meta-data tables to be created in the main database. Or it would be helpful if I can have them in another local or in-memory db like h2db.
I've also added spring-batch-initialize-schema=never already, which was the case with most answers to similar questions on here, but that didn't help.
Edit:
I resolved the Invalid Object name error by preventing the metadata tables from being created into the main database by extending the DefaultBatchConfigurer Class and Overriding the setDataSource method, thus having them created in the in-memory map-repository. Now I want to try two options:
How to have the meta data tables to be created in a local db or in-memory db like h2db.
Or If I have the meta data tables created already in the main database, in a different schema than my main table I'm fetching from. How to point my job to those meta-data tables in another schema, to store the job and step details data in those.
#Configuration
public class SpringBatchConfig extends DefaultBatchConfigurer{
#Override
public void setDataSource(DataSource datasource) {
}
...
My application.properties file looks like this:
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring-batch-initialize-schema=never
spring.batch.job.enabled=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect
I've created a demo with two datasources. Batch metadata will sotre in H2 DB and the Job datasource is Azure SQL.
Here is the project structure:
We need define a DataSourceConfig class and use #Primary annotation for DataSource bean:
#Configuration
public class DataSourceConfig {
#Bean(name = "mssqlDataSource")
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource appDataSource(){
return DataSourceBuilder.create().build();
}
#Bean(name = "h2DataSource")
#Primary
// #ConfigurationProperties(prefix="spring.datasource.h2")
public DataSource h2DataSource() {
return DataSourceBuilder.create()
.url("jdbc:h2:mem:thing:H2;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE")
.driverClassName("org.h2.Driver")
.username("sa")
.password("")
.build();
}
}
In the ItemReaderDbDemo class, we use #Autowired #Qualifier("mssqlDataSource") to specify the dataSource in the Spring Batch task:
#Configuration
public class ItemReaderDbDemo {
//generate task Object
#Autowired
private JobBuilderFactory jobBuilderFactory;
//Step exec tasks
//generate step Object
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Autowired
#Qualifier("mssqlDataSource")
private DataSource dataSource;
#Autowired
#Qualifier("dbJdbcWriter")
private ItemWriter<? super Todo> dbJdbcWriter;
#Bean
public Job itemReaderDbDemoJob() {
return jobBuilderFactory.get("itemReaderDbDemoJob").start(itemReaderDbStep()).build();
}
#Bean
public Step itemReaderDbStep() {
return stepBuilderFactory.get("itemReaderDbStep")
.<Todo,Todo>chunk(2)
.reader(dbJdbcReader())
.writer(dbJdbcWriter)
.build();
}
#Bean
#StepScope
public JdbcPagingItemReader<Todo> dbJdbcReader() {
JdbcPagingItemReader<Todo> reader = new JdbcPagingItemReader<Todo>();
reader.setDataSource(dataSource);
reader.setFetchSize(2);
reader.setRowMapper(new RowMapper<Todo>() {
#Override
public Todo mapRow(ResultSet rs, int rowNum) throws SQLException {
Todo todo = new Todo();
todo.setId(rs.getLong(1));
todo.setDescription(rs.getString(2));
todo.setDetails(rs.getString(3));
return todo;
}
});
SqlServerPagingQueryProvider provider = new SqlServerPagingQueryProvider();
provider.setSelectClause("id,description,details");
provider.setFromClause("from dbo.todo");
//sort
Map<String,Order> sort = new HashMap<>(1);
sort.put("id", Order.DESCENDING);
provider.setSortKeys(sort);
reader.setQueryProvider(provider);
return reader;
}
}
Here is my application.properties:
logging.level.org.springframework.jdbc.core=DEBUG
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.jdbcUrl=jdbc:sqlserver://josephserver2.database.windows.net:1433;database=<Your-Database-Name>;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;
spring.datasource.username=<Your-UserName>
spring.datasource.password=<Your-Password>
spring.datasource.initialization-mode=always
It return expected result from my Azure SQL. By the way, my Azure sql username does not have create permissions for the database.
The result shows:
How to have the meta data tables to be created in a local db or in-memory db like h2db.
You can use spring.batch.initialize-schema=embedded for that.
Or If I have the meta data tables created already in the main database, in a different schema than my main table I'm fetching from. How to point my job to those meta-data tables in another schema, to store the job and step details data in those.
spring batch works against a datasource, not a particular schema. If meta-data tables are in a different schema, then you need to create a second datasource pointing to that schema and set it on the job repository.
I know this post is a little bit old, but I'd like to give an update.
For newer versions of Spring Boot spring.batch.initialize-schema is deprecated.
I'm using Spring Boot 2.7.1 and the newer property is spring.batch.jdbc.initialize-schema.
In my case: when I was receiving the error message was due that the user did not have the CREATE TABLE permission to create the corresponding spring bacth tables.
Adding the permissions fix the issue.
I got a MS SQL Server (2008) database with two schemas, dbo as default for general purpose e.g. authentication and myapp for domain objects. I want to have JPA-Entitys in both Schemas. I use SpringBoot for configuration.
Entitys tables are created in the right schema as they should, e.g. myschema.job, but relationship tables, e.g. Job_Employee are created within the default schema dbo. How can I set in whicht schema automatically created tables are stored (without changing the default schema as this just shifts the problem)?
#Entity
#Table(schema="myschema")
public class Job {[...]
My application.yml looks like:
spring:
profiles: dev
datasource:
datasource1:
url: jdbc:sqlserver://localhost;databaseName=mydb;schema=myschema
username: SA
password: ###
datasource2:
url: jdbc:sqlserver://localhost;databaseName=mydb;schema=dbo
username: SA
password: ###
jpa:
show-sql: true
hibernate.ddl-auto : create-drop
properties:
hibernate.dialect: org.hibernate.dialect.SQLServer2012Dialect
hibernate.default_schema: dbo
And the datasources are Configured in
#Configuration
#EnableJpaRepositories
public class JPAConfiguration {
#Bean
#Primary
#ConfigurationProperties("spring.datasource.datasource1")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("spring.datasource.datasource1")
public DataSource firstDataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder().build();
}
#Bean
#ConfigurationProperties("spring.datasource.datasource2")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("spring.datasource.datasource2")
public DataSource secondDataSource() {
return secondDataSourceProperties().initializeDataSourceBuilder().build();
}
}
Thanks!
The answer is: every Collection must be mapped to the right schema as well with #JoinTable annotation.
E.g. in our Case:
#JoinTable(schema="myschema")
#OneToMany(cascade=CascadeType.ALL)
#Column(nullable = false)
private List<Employee> employee;
This results in a table calles myschema.job_employee.
I am using Spring Boot to initiate a camel route that uses Camel-sql to query MySQL DB and call a REST service.
application.properties
db.driver=com.mysql.jdbc.Driver
db.url=mysql://IP:PORT/abc
db.username=abc
db.password=pwd
Application.java
public static void main(String[] args) {
SpringApplication.run(WlEventNotificationBatchApplication.class, args);
}
DataSourceConfig.java
public class DataSourceConfig {
#Value("${db.driver}")
public String dbDriver;
#Value("${db.url}")
public String dbUrl;
#Value("${db.username}")
public String dbUserName;
#Value("${db.password}")
public String dbPassword;
#Bean("dataSource")
public DataSource getConfig() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(dbDriver);
dataSource.setUrl(dbUrl);
dataSource.setUsername(dbUserName);
dataSource.setPassword(dbPassword);
return dataSource;
}
}
WLRouteBuilder.java
#Component
public class WLRouteBuilder extends RouteBuilder {
#Autowired
private NotificationConfig notificationConfig;
#Autowired
private DataSource dataSource;
#Override
public void configure() throws Exception {
from("direct:eventNotification")
.to("sql:"+notificationConfig.getSqlQuery()+"?dataSource="+dataSource)
.process(new RowMapper())
.log("${body}");
}
}
I see the below error when I run, found out that Camel is unable to find DataSource bean in registry. I am quite not sure how to inject "DataSource" to Registry in Spring Boot using Java DSL.
?dataSource=org.springframework.jdbc.datasource.DriverManagerDataSource%40765367 due to: No bean could be found in the registry for: org.springframework.jdbc.datasource.DriverManagerDataSource#765367 of type: javax.sql.DataSource
Its the name of the bean that Camel uses in the uri, where you refer to it using the # syntax as documented here: http://camel.apache.org/how-do-i-configure-endpoints.html (referring beans)
So something alike
.to("sql:"+notificationConfig.getSqlQuery()+"?dataSource=#dataSource"
Where dataSource is the name of the bean that creates the DataSource, which you can give another name eg
#Bean("myDataSource")
And then the Camel SQL endpoint is
.to("sql:"+notificationConfig.getSqlQuery()+"?dataSource=#myDataSource"
So i followed this tutorial https://spring.io/guides/tutorials/spring-security-and-angular-js/
But i have trouble figuring out how to add the database.
I have added this to the properties files so i have connection to the database
spring.datasource.url=jdbc:mysql://localhost:3306/login
spring.datasource.username=root
spring.datasource.password=root
in sql i create a table as follow
CREATE TABLE IF NOT EXISTS `login`.`users` (
`idusers` INT NOT NULL AUTO_INCREMENT COMMENT '',
`username` VARCHAR(45) NULL COMMENT '',
`password` VARCHAR(256) NULL COMMENT '',
`authority` VARCHAR(45) NULL COMMENT '',
PRIMARY KEY (`idusers`) COMMENT '')
ENGINE = InnoDB;
and added some users.
I want this to replace this with the database.
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
.and()
.withUser("admin").password("admin").roles("USER", "ADMIN", "READER", "WRITER")
.and()
.withUser("audit").password("audit").roles("USER", "ADMIN", "READER");
}
And something i'm confused of is of that i still need the Principal user when using my own database.
#RequestMapping("/user")
public Principal user(Principal user) {
System.out.println(user.getName());
System.out.println(user.toString());
return user;
}
Take a look to the documentation (http://docs.spring.io/spring-security/site/docs/4.0.2.RELEASE/reference/htmlsingle/#jc-authentication-jdbc)
and see how you'll need to change your globalUserDetails method to use a jdbcAuthentication instead of an inMemoryAuthentication :
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
The actual configuration is suited for in memory database as it creates a new schema upon initialisation. In your case you should change it to something like :
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(/* set a query to suit your model*/)
.authoritiesByUsernameQuery(/* set a query to suit your model*/)
.groupAuthoritiesByUsername(/* set a query to suit your model*/);
}
Principal is just an interface that gives you access to the currently logged in user, no more no less.
More on Spring MVC + Security here : http://docs.spring.io/spring-security/site/docs/4.0.2.RELEASE/reference/htmlsingle/#mvc
http://docs.spring.io/spring-security/site/docs/4.0.2.RELEASE/reference/htmlsingle/#mvc-authentication-principal