I've got a problem with my Spring Boot app JPA configuration. I've got two profiles - dev (H2 db) and prod (PostgreSQL). I want to manually set up JPA without Spring Boot "magic", so I've created configuration class shown below
#Configuration
#EnableTransactionManagement
public class PersistenceContext {
#Primary
#Bean
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
#Bean
public DataSource dataSource(DataSourceProperties properties) {
return properties
.initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource(dataSourceProperties()));
em.setPackagesToScan("model");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return em;
}
#Bean
public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
The problem occurs when I want to test saving to database in the dev profile. When I'm performing test I've got this error :
10:37:47.951 [main] DEBUG org.hibernate.SQL - insert into Book (id, author, bookType, bookstore, new_price, old_price, title, url) values (null, ?, ?, ?, ?, ?, ?, ?)
10:37:47.955 [main] DEBUG o.h.e.jdbc.spi.SqlExceptionHelper - could not prepare statement [insert into Book (id, author, bookType, bookstore, new_price, old_price, title, url) values (null, ?, ?, ?, ?, ?, ?, ?)]
org.h2.jdbc.JdbcSQLException: Table "BOOK" not found; SQL statement:
insert into Book (id, author, bookType, bookstore, new_price, old_price, title, url) values (null, ?, ?, ?, ?, ?, ?, ?) [42102-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
My application-dev.properties file looks like this
spring.datasource.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.platform=h2
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.generate-ddl=true
spring.data.jpa.repositories.enabled=true
spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.show-sql=true
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
I figured out that it might be a problem with spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop, cause when I set this property via property map inside PersistenceContext class it's working properly. I don't know how to set it correctly via a properties file. Thank you in advance for your help.
spring.jpa.hibernate.ddl-auto=update and spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop does the same thing. But since you are opted not using Spring Autoconfiguration Magic these properties are ineffective.
So you have to use JPA Property Map and set it in LocalContainerEntityManagerFactoryBean.
Related
After I changed the PostgreSQL 13 database columns tags to jsonb, throw error when execute sql:
Caused by: org.springframework.jdbc.BadSqlGrammarException:
### Error updating database. Cause: org.postgresql.util.PSQLException: ERROR: column "tags" is of type jsonb but expression is of type character varying
Hint: You will need to rewrite or cast the expression.
Position: 519
### The error may exist in class path resource [mybatis/mapper/dolphin/RssSubSourceMapper.xml]
### The error may involve com.dolphin.soa.post.dao.RssSubSourceMapper.updateByPrimaryKeySelective-Inline
### The error occurred while setting parameters
### SQL: UPDATE rss_sub_source SET sub_url = ?, created_time = ?, updated_time = ?, sub_status = ?, rss_type = ?, standard_type = ?, standard_version = ?, cron = ?, trigger_count = ?, next_trigger_time = ?, sub_name = ?, last_trigger_time = ?, intro = ?, failed_count = ?, frequency_month = ?, reputation = ?, rep_latest_refresh_time = ?, scrapy_take_time = ?, censor_status = ?, etag = ?, last_modified = ?, editor_pick = ?, fav_icon_url = ?, dynamic_interval = ?, creator = ?, tags = ?, article_count = ?, article_count_latest_refresh_time = ?, comment_rss = ?, part_output = ? WHERE id = ?
### Cause: org.postgresql.util.PSQLException: ERROR: column "tags" is of type jsonb but expression is of type character varying
Hint: You will need to rewrite or cast the expression.
Position: 519
; bad SQL grammar []; nested exception is org.postgresql.util.PSQLException: ERROR: column "tags" is of type jsonb but expression is of type character varying
Hint: You will need to rewrite or cast the expression.
Position: 519
at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:239) ~[spring-jdbc-5.3.19.jar!/:5.3.19]
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70) ~[spring-jdbc-5.3.19.jar!/:5.3.19]
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:91) ~[mybatis-spring-2.0.6.jar!/:2.0.6]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441) ~[mybatis-spring-2.0.6.jar!/:2.0.6]
at com.sun.proxy.$Proxy135.update(Unknown Source) ~[?:?]
at org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate.java:288) ~[mybatis-spring-2.0.6.jar!/:2.0.6]
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:67) ~[mybatis-3.5.6.jar!/:3.5.6]
at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:152) ~[mybatis-3.5.6.jar!/:3.5.6]
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85) ~[mybatis-3.5.6.jar!/:3.5.6]
at com.sun.proxy.$Proxy152.updateByPrimaryKeySelective(Unknown Source) ~[?:?]
what should I do to handle the jsonb in spring boot application when using mybatis?
Finally add the JsonTypeHandler fixed this problem:
package misc.config.mybatis;
import com.alibaba.fastjson.JSON;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import org.postgresql.util.PGobject;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
#MappedTypes({Object.class})
public class JsonTypeHandler extends BaseTypeHandler<Object> {
private Class<Object> clazz;
public JsonTypeHandler(Class<Object> clazz) {
this.clazz = clazz;
}
public JsonTypeHandler() {
}
private static final PGobject jsonObject = new PGobject();
#Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
jsonObject.setType("json");
jsonObject.setValue(JSON.toJSONString(parameter));
ps.setObject(i, jsonObject);
}
#Override
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
return JSON.parseObject(rs.getString(columnName), clazz);
}
#Override
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return JSON.parseObject(rs.getString(columnIndex), clazz);
}
#Override
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return JSON.parseObject(cs.getString(columnIndex), clazz);
}
}
in the mybatis mapper xml, specify the custom JsonTypeHandler like this:
<result column="tags" jdbcType="OTHER" property="tags" typeHandler="misc.config.mybatis.JsonTypeHandler" />
if using mybatis code auto generator, add code generator config like this:
<table tableName="rss_sub_source"
enableCountByExample="true"
enableUpdateByExample="true"
enableDeleteByExample="true"
enableSelectByExample="true"
selectByExampleQueryId="true">
<generatedKey column="ID" sqlStatement="JDBC" identity="true" />
<columnOverride column="tags" jdbcType="OTHER" typeHandler="misc.config.mybatis.JsonTypeHandler"/>
</table>
I'm getting this exception:
org.h2.jdbc.JdbcSQLException:
Table "CUSTOMERS" not found; SQL statement:
SELECT * FROM CUSTOMERS
This is the H2 Console. I have created a table there:
I have the application.yml file. I have tried to add DB_CLOSE_DELAY=-1 and DATABASE_TO_UPPER=false as well:
spring:
database:
url: jdbc:h2:mem:testdb
h2:
console.enabled: true
Also, I have a configuration class, where I have created the H2 Embedded Database:
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
Finally, the query. The table is named CUSTOMERS:
public List<Customer> getAll() {
return jdbcTemplate.query("SELECT * FROM CUSTOMERS", (resultSet, rowNum) -> {
Customer customer = new Customer();
customer.setId(resultSet.getLong("id"));
customer.setName(resultSet.getString("name"));
customer.setAge(resultSet.getInt("age"));
return customer;
});
}
What should I do?
I had the same concern as you for a few days.
I solved it by adding this:
;TRACE_LEVEL_FILE=3;TRACE_LEVEL_SYSTEM_OUT=3
ie : jdbc:h2:mem:testdb;TRACE_LEVEL_FILE=3;TRACE_LEVEL_SYSTEM_OUT=3
It helps to know why H2 has a problem.
Usually it is a keyword problem.
You can ignore it by using NON_KEYWORDS : https://www.h2database.com/html/commands.html#set_non_keywords
I am continuously getting below error , I did enable transaction with #EnableTransactionManagement, but still somehow transaction is not invoked in DefaultTokenServices.
Any help will be much appreciated
Note: it was working with spring-boot 1.5 and recently I upgraded to 2.1
2020-11-19 18:27:12.385 ERROR 49065 [tomcat-exec-2] - o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: TransientDataAccessResourceException, PreparedStatementCallback; SQL [insert into oauth_access_token (token_id, token, authentication_id, user_name, client_id, authentication, refresh_token) values (?, ?, ?, ?, ?, ?, ?)]; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
org.springframework.dao.TransientDataAccessResourceException: PreparedStatementCallback; SQL [insert into oauth_access_token (token_id, token, authentication_id, user_name, client_id, authentication, refresh_token) values (?, ?, ?, ?, ?, ?, ?)]; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:110)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
solution
I am able to fix with hack by manually attaching transaction to oauth jdbctokenservice.
private static final String AOP_POINTCUT_EXPRESSION = "execution (* org.springframework.security.oauth2.provider.token.store.JdbcTokenStore.*(..))";
#Autowired
public void txAdvice(TransactionInterceptor txAdvice) throws NoSuchMethodException {
DefaultTransactionAttribute required = new DefaultTransactionAttribute();
MethodMapTransactionAttributeSource source = new MethodMapTransactionAttributeSource();
final Method method = JdbcTokenStore.class.getMethod("storeAccessToken", OAuth2AccessToken.class, OAuth2Authentication.class);
source.addTransactionalMethod(method, required);
txAdvice.setTransactionAttributeSource(source);
}
#Bean
public Advisor txAdviceAdvisor(TransactionInterceptor txAdvice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice);
}
I also created issue with spring-security-oauth but seems it is not supposed to support spring-boot 2.x.
Any Brilliant mind wanna help on why Transaction was not invoked in DefaultTokenServices.
Solution
Create Bean for DefaultTokenServices and pass it to configurer
#Autowired
private DefaultTokenServices tokenServices;
#Bean
#Primary
public DefaultTokenServices defaultTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore);
return defaultTokenServices;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authorizationCodeServices(authorizationCodeServices())
.tokenStore(tokenStore)
.authenticationManager(auth)
.addInterceptor(handlerInterceptor)
.tokenServices(tokenServices)
.approvalStoreDisabled();
}
link: https://github.com/spring-projects/spring-security-oauth/issues/1900
I am trying to insert some objects in a BatchUpdate operation into the H2 Db with Spring Boot, and I am having problems doing so. The implementation does not think that the table is created. I get an error stack trace given below.
org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar
[INSERT INTO Expired_Chats(chat_id, username, chat_text, expiration_date) VALUES(?, ?, ?, ?)]; nested exception is org.h2.jdbc.JdbcSQLException: Table "EXPIRED_CHATS" not found; SQL statement:
INSERT INTO Expired_Chats(chat_id, username, chat_text, expiration_date) VALUES(?, ?, ?, ?) [42102-196]
I realise that the table is not being created, but I don't know why. According to the Spring Boot documentation, and I quote
Spring Boot can automatically create the schema (DDL scripts) of your DataSource and initialize it (DML scripts): it loads SQL from the standard root classpath locations schema.sql and data.sql, respectively.
My schema.sql is given below. It attempts to create two tables Chats and Expired_Chats. Each SQL statement is on a separate line for purposes of readability.
CREATE TABLE IF NOT EXISTS Chats(username VARCHAR(20), chat_text VARCHAR(256), chat_id NOT NULL BIGINT, expiration_date TIMESTAMP, PRIMARY KEY(chat_id));
CREATE INDEX IF NOT EXISTS username_index on Chats(username);
CREATE INDEX IF NOT EXISTS chat_id_index on Chats(chat_id);
CREATE INDEX IF NOT EXISTS expiration_date_index on Chats(expiration_date);
CREATE TABLE IF NOT EXISTS Expired_Chats(username VARCHAR(20), chat_text VARCHAR(256), chat_id NOT NULL BIGINT, expiration_date TIMESTAMP, PRIMARY KEY(chat_id));
CREATE INDEX IF NOT EXISTS username_index on Expired_Chats(username);
CREATE INDEX IF NOT EXISTS chat_id_index on Expired_Chats(chat_id);
CREATE INDEX IF NOT EXISTS expiration_date_index on Expired_Chats(expiration_date);
When I run my unit test in Eclipse, I see the following console log. Both schema.sql and cleanup.sql are being executed.
2017-11-08 18:16:26.635 INFO 24851 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from class path resource [schema.sql]
2017-11-08 18:16:26.637 INFO 24851 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from class path resource [schema.sql] in 2 ms.
2017-11-08 18:16:26.639 DEBUG 24851 --- [ main] c.underarmour.assignment.ChatRecordDao : Inserting 1 record(s) into the expired chat table.
2017-11-08 18:16:26.639 DEBUG 24851 --- [ main] c.underarmour.assignment.ChatRecordDao : Triggering 1 batch insert(s) to expired chat table.
2017-11-08 18:16:26.651 INFO 24851 --- [ main] o.s.b.f.xml.XmlBeanDefinitionReader : Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
2017-11-08 18:16:26.723 INFO 24851 --- [ main] o.s.jdbc.support.SQLErrorCodesFactory : SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase, Hana]
I am trying to insert data into the database using the following code. It uses Spring JDBC's Batch Update APIs.
public void insertDataInExpiredTable(Set<ChatRecord> chatRecords) {
StringBuilder sb = new StringBuilder();
sb.append("INSERT INTO Expired_Chats(chat_id, username, chat_text, expiration_date) ");
sb.append("VALUES(?, ?, ?, ?)");
Instant instant = Instant.now();
ZonedDateTime zt = instant.atZone(ZoneOffset.UTC);
List<ChatRecord> allRecords = new ArrayList<>(chatRecords);
for (int i = 0; i < allRecords.size(); i++) {
this.jdbcTemplate.batchUpdate(sb.toString(), new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, allRecords.get(i).getChatId());
ps.setString(2, allRecords.get(i).getUsername());
ps.setString(3, allRecords.get(i).getText());
ps.setTimestamp(4, Timestamp.from(zt.toInstant()));
}
#Override
public int getBatchSize() {
// TODO Auto-generated method stub
return 100;
}
});
}
}
My unit test configuration is as follows
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes= {ChatRecordDao.class})
#Sql(executionPhase=ExecutionPhase.BEFORE_TEST_METHOD,scripts="classpath:/schema.sql")
#Sql(executionPhase=ExecutionPhase.AFTER_TEST_METHOD,scripts="classpath:/cleanup.sql")
public class ChatRecordDaoTest {
#Autowired
private DataSource dataSource;
#Autowired
private ChatRecordDao chatRecordDao;
#Test
public void testInsertDataInExpiredTable() throws Exception {
ChatRecord expected = new ChatRecord();
expected.setUsername("test");
expected.setText("text");
expected.setTimeout(14400);
expected.setChatId(1L);
Set<ChatRecord> expiredInserted = new LinkedHashSet<>();
expiredInserted.add(expected);
this.chatRecordDao.insertDataInExpiredTable(expiredInserted);
}
}
The clean up script consists of the following statements
DROP TABLE IF EXISTS Chats;DROP TABLE IF EXISTS Expired_Chats;
First: I'm probably just making a stupid mistake.
I'm working on converting an old project of mine from Spring XML to Javaconfig. The database is an in-memory HSQLDB database. Unfortunately, it's giving me this error:
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL via JDBC Statement
(stacktrace)
Caused by: java.sql.SQLSyntaxErrorException: user lacks privilege or object not found: PUBLIC.T_AUTHORITY
(stacktrace)
Caused by: org.hsqldb.HsqlException: user lacks privilege or object not found: PUBLIC.T_AUTHORITY
Below are my PersistenceConfig.java and my SQL script:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "org.jason.application.repository.jpa",
entityManagerFactoryRef = "entityManagerFactoryBean")
public class ApplicationPersistenceConfig {
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(emf);
return jpaTransactionManager;
}
#Bean
public LocalContainerEntityManagerFactoryBean getEntityManagerFactoryBean(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setPersistenceUnitName("default");
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactory.setJpaDialect(new HibernateJpaDialect());
entityManagerFactory.setPackagesToScan("org.jason.application.repository.model");
entityManagerFactory.setJpaPropertyMap(hibernateJpaProperties());
return entityManagerFactory;
}
#Bean
public DataSource getDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:mem:testdb");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
private Map<String, ?> hibernateJpaProperties() {
HashMap<String, String> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.import_files", "insert-data.sql");
properties.put("hibernate.hbm2ddl.auto", "create-drop");
properties.put("hibernate.show_sql", "false");
properties.put("hibernate.format_sql", "false");
properties.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");
properties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
properties.put("hibernate.c3p0.min_size", "2");
properties.put("hibernate.c3p0.max_size", "5");
properties.put("hibernate.c3p0.timeout", "300"); // 5mins
return properties;
}
}
and
CREATE TABLE PUBLIC.T_USER (
USERID INTEGER NOT NULL PRIMARY KEY,
USERNAME VARCHAR_IGNORECASE(50) NOT NULL,
PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,
ENABLED BOOLEAN NOT NULL,
CREATE UNIQUE INDEX IX_USERNAME ON T_USER(USERNAME);
CREATE TABLE PUBLIC.T_AUTHORITY (
AUTHORITYID INTEGER NOT NULL PRIMARY KEY,
USERID INTEGER NOT NULL,
-- USERNAME VARCHAR_IGNORECASE(50) NOT NULL,
AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,
CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY(USERID) REFERENCES USERS(USERID));
CREATE UNIQUE INDEX IX_AUTH_USERNAME ON T_AUTHORITY (USERID,AUTHORITY);
INSERT INTO T_USER(USERNAME, PASSWORD, ENABLED) VALUES (1, 'jason','password', true);
INSERT INTO T_AUTHORITY(AUTHORITYID, USERID, AUTHORITY) VALUES (1, 1, "ROLE_ADMIN");
Can anyone see whatever stupid mistake I made?
Jason
It was a dumb mistake, just like I thought.
The following two hibernate properties are incompatible with one another:
properties.put("hibernate.hbm2ddl.import_files", "insert-data.sql");
properties.put("hibernate.hbm2ddl.auto", "create-drop");
Both have the effect of creating the schema.