I am studying java and spring and try to make my first spring mvc app. At first I made console app without spring mvc I used just only dao and service layers with JdbcTemplate. I have config file where I make Beans for DataSource and DataSourceInitializer for initializing database.
#EnableWebMvc
#EnableTransactionManagement
#ComponentScan("ru.stepev")
#PropertySource("classpath:config.properties")
public class UniversityConfig implements WebMvcConfigurer {
#Value("${driver}")
private String driver;
#Value("${url}")
private String url;
#Value("${user}")
private String user;
#Value("${pass}")
private String pass;
#Value("${schema}")
private Resource schema;
#Value("${data}")
private Resource data;
#Bean
public JdbcTemplate jdbcTamplate(DataSource dateSourse) {
return new JdbcTemplate(dateSourse);
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(user);
dataSource.setPassword(pass);
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
#Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator();
resourceDatabasePopulator.addScript(schema);
resourceDatabasePopulator.addScript(data);
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
dataSourceInitializer.setDataSource(dataSource);
dataSourceInitializer.setDatabasePopulator(resourceDatabasePopulator);
return dataSourceInitializer;
}
private final ApplicationContext applicationContext;
public UniversityConfig(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("/WEB-INF/view/");
templateResolver.setSuffix(".jsp");
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
registry.viewResolver(resolver);
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
All shemes and data I stores in resourse folder. Without mvc everything were be ok. But when I have added MVC then I got java.io.FileNotFoundException org.springframework.jdbc.datasource.init.CannotReadScriptException: Cannot read SQL script from ServletContext resource [/schema.sql]; nested exception is java.io.FileNotFoundException: Could not open ServletContext resource [/schema.sql]. DataSourceInitializer can't find schema.sql when I create my datebase. I have tried to solve this problem but I couldn't. I have next structure of project.enter image description here
I wil be very thaks for help me with this problem and sorry for my bad english.
I've found my mistake. I had an exception because I didn't put the word classpath before the file name in the properties file.
schema=classpath:schema.sql
data=classpath:data.sql
Related
I am new to Spring Java and a little bit confused about where to use #Bean annotation.
See the following code for instance:
#Configuration
#EnableTransactionManagement
public class HibernateConfig {
#Autowired
private Environment env;
private final Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
properties.put("hibernate.format_sql", env.getProperty("hibernate.format_sql"));
properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
properties.put("hibernate.max_fetch_depth", env.getProperty("hibernate.max_fetch_depth"));
properties.put("hibernate.cache.use_second_level_cache",env.getProperty("hibernate.cache.use_second_level_cache"));
properties.put("hibernate.cache.use_minimal_puts", env.getProperty("hibernate.cache.use_minimal_puts"));
properties.put("hibernate.connection.release_mode", env.getProperty("hibernate.connection.release_mode"));
properties.put("hibernate.cache.use_query_cache",env.getProperty("hibernate.cache.use_query_cache"));
return properties;
}
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean lcemfb = new LocalContainerEntityManagerFactoryBean();
lcemfb.setJpaVendorAdapter(getJpaVendorAdapter());
lcemfb.setDataSource(dataSource());
lcemfb.setPersistenceUnitName("entityManagerFactory");
lcemfb.setPackagesToScan("com.sha.microservicecoursemanagement.model");
lcemfb.setJpaProperties(hibernateProperties());
return lcemfb;
}
#Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager(entityManagerFactory().getObject());
}
#Bean
public JpaVendorAdapter getJpaVendorAdapter() {
JpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
return adapter;
}
}
Now in the above code, dataSource, LocalContainerEntityManagerFactoryBean, PlatformTransactionManager and JpaVendorAdapter these functions have attribute as #Bean.
Now, what I have read on the internet is #Bean can only be given to the class types, not to the functions.
Can someone clear my confusion, please?
The #Bean can be applied to method, if the class is annotated with #Configuration. Refer this link
Following example where I am using #Bean
#Configuration
public class ClientDataSourceConfig {
#Value("${spring.datasource.driver}")
private String driver;
#Value("${spring.datasource.url}")
private String url;
#Value("${spring.datasource.username}")
private String username;
#Value("${spring.datasource.password}")
private String password;
public ClientDataSourceRouter dataSource;
#Bean(name = "getDataSource")
public ClientDataSourceRouter getDataSource() throws Exception {
dataSource = new ClientDataSourceRouter();
dataSource.init(driver, url, username, password);
return dataSource;
}
}
Yes #Bean can only be given to the Class types. In your functions you can see that you are returning class instances such as DataSource, LocalContainerEntityManagerFactoryBean etc. You are not adding #Bean annotation to the function. what you are doing is, telling Spring to return a bean from that class. for an example,
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
In here you are telling Spring to return a DataSource Object. Unless if you have #Bean annocation , you can't autowire the datasource object. Therefor #Bean allow you to use #Autowired DataSource dataSource;. These beans are managed by Spring IoC container.
I'm building a SpringBoot app that loads internationalisation messages from Database.
(Followed this tutorial)
From that, I had to create a ThymeleafConfiguration class and set a SpringTemplateEngine Bean. The tutorial gave only a rough idea about this configuration (only configured the message source, but not the other templateengine configurations), so it broke my controller page rendering (controller is now returning string instead of view).
I'm trying to configure the rest of my TemplateEngine (such as the TemplateResolver, which I think is the reason why the rendering is not correct), however, I can't figure out how to do it correctly [I keep getting "An error happened during template parsing (template: "ServletContext resource [/templates/login.html]")" messages].
How to configure SpringTemplateEngine correctly?
My configuration so far:
#Configuration
public class ThymeleafConfiguration implements WebMvcConfigurer, ApplicationContextAware{
private ApplicationContext applicationContext;
#Autowired
private DatabaseMessageSource databaseMessageSource;
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(thymeleafTemplateEngine());
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
private ITemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
return resolver;
}
#Bean
public SpringTemplateEngine thymeleafTemplateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
engine.setTemplateEngineMessageSource(databaseMessageSource);
return engine;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Controller class:
#Controller
public class ApplicationController {
#RequestMapping("/home")
public String home() {
return "/home.html";
}
#RequestMapping("/core/index")
public String index() {
return "/core/index.html";
}
#RequestMapping("/login")
public String login() {
return "login";
}
The problem is with
private ITemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("/templates/"); // Here!!!
resolver.setSuffix(".html");
return resolver;
}
Since the templates are within src/main/resources, it has to be pointed to classpath, as follows:
#Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("classpath:/templates/"); // It works after adding 'classpath:'
resolver.setSuffix(".html");
return resolver;
}
Also, there's a little change from the interface to a implementation class that should not interfere with the result.
This i my database configuration and app.properties
#Configuration
public class DatabaseConfig {
#Bean
#ConfigurationProperties(prefix = "datasource.two")
public HikariConfig hikariConfig2() {
return new HikariConfig();
}
#Bean
public DataSource dataSource2() {
HikariDataSource hds = new HikariDataSource(new hikariConfig2());
return hds;
}
#Bean
public JdbcTemplate jdbcTemplate2() {
return new JdbcTemplate(dataSource2());
}
#Bean
#ConfigurationProperties(prefix = "datasource.one")
public HikariConfig hikariConfig2() {
return new HikariConfig();
}
#Bean
public DataSource dataSource1() {
HikariDataSource hds = new HikariDataSource(hikariConfig1());
return hds;
}
#Bean
public JdbcTemplate jdbcTemplate1() {
return new JdbcTemplate(dataSource1());
}
}
and
datasource.one.jdbc-url=...
datasource.one.username=...
datasource.one.password=...
datasource.one.driver-class-name=...
datasource.two.jdbcUrl=...
datasource.two.username=...
datasource.two.password=...
datasource.two.driver-class-name=...
How should I create global transaction manager which will support two phase commit. I want to run this app inside docker with Tomcat so no JavaEE.
Also no hibernate. I am using sql-processor with spring-stack:
PS: In some very old article I found this but idk if it rigth sollution
#Bean
public DataSourceTransactionManager tm1() {
DataSourceTransactionManager txm = new DataSourceTransactionManager(refDataSource());
return txm;
}
#Bean
public DataSourceTransactionManager tm2() {
DataSourceTransactionManager txm = new DataSourceTransactionManager(logDataSource());
return txm;
}
#Bean(name = "transactionManager")
#Primary
public PlatformTransactionManager transactionManager() throws Throwable {
return new ChainedTransactionManager(tm1(), tm2());
}
I am working on spring boot multi tenancy application. I have configured multi datasources as shown below :
application.properties
spring.multitenancy.datasource1.url=jdbc:mysql://localhost:3306/db1
spring.multitenancy.datasource1.username=root
spring.multitenancy.datasource1.password=****
spring.multitenancy.datasource1.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.multitenancy.datasource2.url=jdbc:mysql://localhost:3306/db2
spring.multitenancy.datasource2.username=root
spring.multitenancy.datasource2.password=****
spring.multitenancy.datasource2.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.multitenancy.datasource3.url=jdbc:mysql://localhost:3306/db3
spring.multitenancy.datasource3.username=root
spring.multitenancy.datasource3.password=****
spring.multitenancy.datasource3.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
DataSourceBasedMultiTenantConnectionProviderImpl.java
#Component
public class DataSourceBasedMultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
private static final long serialVersionUID = 8168907057647334460L;
private static final String DEFAULT_TENANT_ID = "tenant_1";
#Autowired
private DataSource dataSource1;
#Autowired
private DataSource dataSource2;
#Autowired
private DataSource dataSource3;
private Map<String, DataSource> map;
#PostConstruct
public void load() {
map = new HashMap<>();
map.put("tenant_1", dataSource1);
map.put("tenant_2", dataSource2);
map.put("tenant_3", dataSource3);
}
#Override
protected DataSource selectAnyDataSource() {
return map.get(DEFAULT_TENANT_ID);
}
#Override
protected DataSource selectDataSource(String tenantIdentifier) {
return map.get(tenantIdentifier);
}
}
MultitenancyProperties.java
#ConfigurationProperties("spring.multitenancy")
public class MultitenancyProperties {
#NestedConfigurationProperty
private DataSourceProperties datasource1;
#NestedConfigurationProperty
private DataSourceProperties datasource2;
#NestedConfigurationProperty
private DataSourceProperties datasource3;
public DataSourceProperties getDatasource1() {
return datasource1;
}
public void setDatasource1(DataSourceProperties datasource1) {
this.datasource1 = datasource1;
}
public DataSourceProperties getDatasource2() {
return datasource2;
}
public void setDatasource2(DataSourceProperties datasource2) {
this.datasource2 = datasource2;
}
public DataSourceProperties getDatasource3() {
return datasource3;
}
public void setDatasource3(DataSourceProperties datasource3) {
this.datasource3 = datasource3;
}
}
MultiTenancyJpaConfiguration.java
#Configuration
#EnableConfigurationProperties(JpaProperties.class)
public class MultiTenancyJpaConfiguration {
#Autowired
private DataSource dataSource;
#Autowired
private JpaProperties jpaProperties;
#Autowired
private MultiTenantConnectionProvider multiTenantConnectionProvider;
#Autowired
private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
Map<String, Object> hibernateProps = new LinkedHashMap<>();
hibernateProps.putAll(jpaProperties.getHibernateProperties(dataSource));
hibernateProps.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
hibernateProps.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
hibernateProps.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
hibernateProps.put(Environment.DIALECT, "org.hibernate.dialect.MySQLDialect");
return builder.dataSource(dataSource).packages(HotelEntity.class.getPackage().getName()).properties(hibernateProps).jta(false).build();
}
}
Application launcher
#SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
#EnableConfigurationProperties(MultitenancyProperties.class)
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
}
When I run the boot application, all tables are created in only first data source.
1) How can I create tables in all data sources on application startup?
2) How to see connections opened/closed for each of the data sources?
3) Is there a better way of configuring multi tenancy application using spring boot for better performance?
As #Alex said, you need differnt EntityManagers, TransactionManager and Datasources. Here is how I would do it
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "dataSource1EntityManagerFactory",
transactionManagerRef = "dataSource1TransactionManager",
basePackageClasses = dataSource1Repository.class)
public class DataSource1Config extends SqlConfig{// Put all common code in base class SqlConfig. If not remove it
#Bean
#Primary
public DataSource dataSource1() {
//create dataSource using MultitenancyProperties::getDataSource1
}
#Primary
#Bean(name = "dataSource1TransactionManager")
PlatformTransactionManager dataSource1TransactionManager(EntityManagerFactory dataSource1EntityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(dataSource1EntityManagerFactory);
return txManager;
}
#Primary
#Bean(name = "dataSource1EntityManagerFactory")
LocalContainerEntityManagerFactoryBean dataSource1EntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource1());
em.setPackagesToScan(dataSource1Repository.class.getPackage().getName(), dataSource1ModelClass.class.getPackage().getName());
em.setPersistenceUnitName("dataSource1Db");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
}
You can create two other classes like this. Remember to use #Primary on only one instace of datasource, transactionmanger and entitymanager(doesn't matter which one). Another word of caution, make sure Repository classes are in different packages for all three databases.
Try adding the following properties:
spring.jpa.generate-ddl=true
You need 2 different persistence factories, not one, each should produce different EntityManagers for different datasources. Also each entity mapping should be marked to been used only with one entity manager.
See full solution here:
http://www.baeldung.com/spring-data-jpa-multiple-databases
I have a profile called "stage", I am trying to implement a web service but whenever I make a request I get some error, whenever I delete the profile annotation it works normally, I dont really know why it is not working with a profile here are some classes:
#SpringBootApplication
public class MyApp{
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("stage");
context.register(MyApp.class);
SpringApplication.run(MyApp.class, args);
}
}
#Profile("stage")
#Configuration
#PropertySource(value = { "classpath:jdbc.properties" })
public class StageDSConfig {
#Value("${jdbc.driverClassName}")
private String driverClassName;
#Value("${jdbc.url}")
private String jdbcURL;
#Value("${jdbc.username}")
private String username;
#Value("${jdbc.password}")
private String password;
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(jdbcURL);
dataSource.setUsername(username);
dataSource.setPassword(password);
DatabasePopulatorUtils.execute(createDatabasePopulator(), dataSource);
return dataSource;
}
private DatabasePopulator createDatabasePopulator() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
databasePopulator.setContinueOnError(true);
databasePopulator.addScript(new ClassPathResource("schema-mysql.sql"));
databasePopulator.addScript(new ClassPathResource("data-mysql.sql"));
return databasePopulator;
}
}
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
#Bean(name = "ws")
public DefaultWsdl11Definition defaultActionWsdl11Definition(XsdSchema actionSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("ActionPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://www.example.com/ws");
wsdl11Definition.setSchema(actionSchema);
return wsdl11Definition;
}
#Bean
public XsdSchema wsSchema() {
return new SimpleXsdSchema(new ClassPathResource("ws.xsd"));
}
}
and this is the error log:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring xml:lang="en">PreparedStatementCallback; bad SQL grammar [INSERT INTO logs(ipAddress,actionn,requestBody) values(?,?,?)]; nested exception is java.sql.SQLSyntaxErrorException: user lacks privilege or object not found: LOGS</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I believe you are not correctly activating the profile. With springBoot, you may set the profile like this:
new SpringApplicationBuilder(MyApp.class).profiles("stage").run(args);
Or, through an environment variable:
System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "stage");
SpringApplication.run(MyApp.class, args);
I think the problem is that you don't actually activate the "stage" profile, so the context is missing all the beans in StageDSConfig class and thus no schema is created