I want to run alters on the embedded derby database after a desktop application has been updated. To do this I attempted to create a JPS repository with my alter queries similar to this:
#Repository
public interface DatabaseUpdates {
#Modifying
#Query("alter table my_table add column my_column clob", nativeQuery = true)
void alterMyTableAddMyColumn();
}
And use it in the updater class like this
#Component
public class DatabaseUpdater {
#Autowired
private DatabaseUpdates databaseUpdates;
private void alterMyTable() {
// some logic that checks it the update needs to happen is here
databaseUpdates.alterMyTableAddMyColumn();
}
}
This throws NoSuchBeanDefinitionException: No qualifying bean of type DatabaseUpdates exceptions.
I also tried having DatabaseUpdates extend org.springframework.data.repository.Repository but then had problems with what to use as the domain type.
I am using spring-boot-starter-data-jpa 1.2.3.RELEASE.
Edit: This is to support a legacy app and I am looking for straight forward ways to support database updates. Database migrations tools are great but not in scope for this fix. I've been wishing this project had been using one for more than a month now and will be integrating one into the next version soon.
That sounds like an awful idea. There are frameworks, which were specifically developed for your usecase and are supported by Spring Boot, like Flyway and Liquibase. You are then able to specify your changes either in SQL or Java code (for Flyway) or in an XML, SQL, YAML or JSON file (for Liquibase). Those frameworks will check which migrations are necessary and they also keep track when a migration was executed.
I wouldn't put this in Spring JPA. If you mention it is temporary, maybe try some JDBC directly and run the queries on the database. Try to log the changes you make in a table (something similar to what liquibase is doing), so you don't run the queries multiple times.
Maybe try to run this before Spring builds your DataSource in your DatabaseConfiguration class.
#Configuration
#EnableJpaRepositories("")
#EnableTransactionManagement
public class DatabaseConfiguration implements EnvironmentAware {
#Bean(destroyMethod = "close")
public DataSource dataSource(DataSourceProperties dataSourceProperties, ApplicationProperties applicationProperties)
throws SQLException {
LOG.debug("Configuring Datasource");
//some code
alterSchema();
return new HikariDataSource(config);
}
private void alterSchema() {
// JDBC connection and queries to alter the schema
}
}
While not a best practice, you can temporarily use native JDBC, acquire connection from your existing DataSource. If you are only adding a column, make sure to check if the object(column) already exist.
Your repository can execute a transaction and build a native query with an EntityManager like the following code
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
#Repository
public class DatabaseUpdates{
#PersistenceContext
private EntityManager entityManager;
//ALTER TABLE `my_table` ADD COLUMN `my_column` VARCHAR(100) AFTER `after_column`;
#Transactional
public void alterMyTableAddMyColumn(String tableName, String columnName,
String columnType, String afterColumnName) {
String query = "ALTER TABLE `" + tableName + "` ADD COLUMN `" + columnName + "` " +
columnType + " AFTER `" + afterColumnName + "`";
entityManager.createNativeQuery(query).executeUpdate();
}
}
Your updater class calls your repository like this
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
#Component
public class DatabaseUpdater {
#Autowired
private DatabaseUpdates databaseUpdates;
private void alterMyTable() {
// some logic that checks it the update needs to happen is here
String tableName = "my_table";
String columnName = "my_column";
String columnType = "VARCHAR(100)";
String afterColumnName = "after_column";
databaseUpdates.alterMyTableAddMyColumn(tableName, columnName,
columnType, afterColumnName);
}
}
Related
I am trying to get db schema name on runtime using spring.jpa.properties.hibernate.default_schema property of spring but it is not resolving the {h-schema} on runtime.
DemoApplication.java
public class DemoApplication
{
public static void main(String[] args)
{
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
Test test = context.getBean(Test.class);
System.err.println("The required count is = "+test.getCount());
}
}
Test.java
#FunctionalInterface
public interface Test
{
public BigDecimal getCount();
}
TestImpl.java
#Service
public class TestImpl implements Test
{
#Autowired
JdbcTemplate jdbcTemplate;
#Override
public BigDecimal getCount()
{
return (BigDecimal) jdbcTemplate.queryForList("select count(*) from {h-schema}test").get(0).values().toArray()[0];
}
}
application.properties
spring.datasource.url=jdbc:oracle:thin:#XXX:YYY/ZZZ
spring.jpa.database-platform=org.hibernate.dialect.Oracle12cDialect
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.username=X1
spring.datasource.password=Y1
spring.jpa.properties.hibernate.default_schema=Z1
#hibernate config
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
spring.jpa.hibernate.ddl-auto=none
logging.level.org.springframework.jdbc.core = TRACE
ErrorLog
Executing SQL query [select count(*) from {h-schema}test]
Exception in thread "main" org.springframework.jdbc.UncategorizedSQLException: StatementCallback; uncategorized SQLException for SQL [select count(*) from {h-schema}test]; SQL state [99999]; error code [17034]; Non supported SQL92 token at position: 22; nested exception is java.sql.SQLException: Non supported SQL92 token at position: 22
I'm not sure how to resolve the spring.jpa.properties.hibernate.default_schema variable from properties file into {h-schema}. please provide me any appropriate approach or some alternative to the issue.
[Edit] : After lots of debugging i found that spring provides two propeties namely spring.datasource.hikari.schema and spring.datasource.hikari.connection-init-sql which may suffice some of the needs but they kind of alter the session as well which means i cannot query the data from the schema i logged in therefore i still would like someone to suggest something related to concept of resolving {h-schema} from properties file so that i can switch schema on need basis in native queries without hardcoding schema names or changing schema sessions explicitly.
In short; since the default schema information declared in your application.properties (or .yml) file, you may get this value with #Value annotation and can use it in your SQL statement.
For "show me the code" ones, your TestImpl.java class should become as following;
#Service
public class TestImpl implements Test
{
#Autowired
JdbcTemplate jdbcTemplate;
#Value("${spring.jpa.properties.hibernate.default_schema}")
private String defaultSchema;
#Override
public BigDecimal getCount()
{
return (BigDecimal) jdbcTemplate.queryForList("select count(*) from " + defaultSchema + ".test").get(0).values().toArray()[0];
}
}
Note: Don't miss adding the "." before your table name.
My Spring-Boot-Mvc-Web application has the following database configuration in application.properties file:
spring.datasource.url=jdbc:h2:tcp://localhost/~/pdk
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
this is the only config I made. No any other configurations made by me anywhere. Nevertheless the Spring and subsystems are automatically recreate database on each web application run. Database is recreated namely on system run while it contains data after application ends.
I was not understanding this defaults and was expecting this is suitable for tests.
But when I started to run tests I found that database is recreated only once. Since tests are executed at no predefined order, this is senseless at all.
So, the question is: how to make any sense? I.e. how to make database recreate before each test as it happens at application first start?
My test class header is follows:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = myapp.class)
//#WebAppConfiguration
#WebIntegrationTest
#DirtiesContext
public class WebControllersTest {
As you see, I tried #DirtiesContext at class level and it didn't help.
UPDATE
I have a bean
#Service
public class DatabaseService implements InitializingBean {
which has a method
#Override
#Transactional()
public void afterPropertiesSet() throws Exception {
log.info("Bootstrapping data...");
User user = createRootUser();
if(populateDemo) {
populateDemos();
}
log.info("...Bootstrapping completed");
}
Now I made it's populateDemos() method to clear all data from database. Unfortunately, it does not called before each test despite #DirtiesContext. Why?
Actually, I think you want this:
#DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
http://docs.spring.io/autorepo/docs/spring-framework/4.2.6.RELEASE/javadoc-api/org/springframework/test/annotation/DirtiesContext.html
#DirtiesContext may be used as a class-level and method-level
annotation within the same class. In such scenarios, the
ApplicationContext will be marked as dirty after any such annotated
method as well as after the entire class. If the
DirtiesContext.ClassMode is set to AFTER_EACH_TEST_METHOD, the context
will be marked dirty after each test method in the class.
You put it on your Test class.
Using the accepted answer in Spring-Boot 2.2.0, I was seeing JDBC syntax errors related to constraints:
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Constraint "FKEFFD698EA2E75FXEERWBO8IUT" already exists; SQL statement:
alter table foo add constraint FKeffd698ea2e75fxeerwbo8iut foreign key (bar) references bar [90045-200]
To fix this, I added #AutoConfigureTestDatabase to my unit test (part of spring-boot-test-autoconfigure):
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest
#DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
#AutoConfigureTestDatabase(replace = Replace.ANY)
public class FooRepositoryTest { ... }
To create the database you have to do what the other answers say with the spring.jpa.hibernate.ddl-auto=create-drop, now if your intent is to pupulate the database on each test then spring provides a very usefull anotation
#Transactional(value=JpaConfiguration.TRANSACTION_MANAGER_NAME)
#Sql(executionPhase=ExecutionPhase.BEFORE_TEST_METHOD,scripts="classpath:/test-sql/group2.sql")
public class GroupServiceTest extends TimeoffApplicationTests {
that is from this package org.springframework.test.context.jdbc.Sql; and you can run a before test method and a after test method. To populate the database.
Regarding creating the database each time, Say you only want your Test to have the create-drop option you can configure your tests with a custom properties with this annotation
#TestPropertySource(locations="classpath:application-test.properties")
public class TimeoffApplicationTests extends AbstractTransactionalJUnit4SpringContextTests{
Hope it helps
If you are looking for an alternative for the #DirtiesContext, this code below will help you. I used some code from this answer.
First, setup the H2 database on the application.yml file on your test resources folder:
spring:
datasource:
platform: h2
url: jdbc:h2:mem:test
driver-class-name: org.h2.Driver
username: sa
password:
After that, create a class called ResetDatabaseTestExecutionListener:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;
public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {
#Autowired
private DataSource dataSource;
public final int getOrder() {
return 2001;
}
private boolean alreadyCleared = false;
#Override
public void beforeTestClass(TestContext testContext) {
testContext.getApplicationContext()
.getAutowireCapableBeanFactory()
.autowireBean(this);
}
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (!alreadyCleared) {
cleanupDatabase();
alreadyCleared = true;
}
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
cleanupDatabase();
}
private void cleanupDatabase() throws SQLException {
Connection c = dataSource.getConnection();
Statement s = c.createStatement();
// Disable FK
s.execute("SET REFERENTIAL_INTEGRITY FALSE");
// Find all tables and truncate them
Set<String> tables = new HashSet<>();
ResultSet rs = s.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='PUBLIC'");
while (rs.next()) {
tables.add(rs.getString(1));
}
rs.close();
for (String table : tables) {
s.executeUpdate("TRUNCATE TABLE " + table);
}
// Idem for sequences
Set<String> sequences = new HashSet<>();
rs = s.executeQuery("SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='PUBLIC'");
while (rs.next()) {
sequences.add(rs.getString(1));
}
rs.close();
for (String seq : sequences) {
s.executeUpdate("ALTER SEQUENCE " + seq + " RESTART WITH 1");
}
// Enable FK
s.execute("SET REFERENTIAL_INTEGRITY TRUE");
s.close();
c.close();
}
}
The code above will reset the database (truncate tables, reset sequences, etc) and is prepared to work with H2 database. If you are using another memory database (like HsqlDB) you need to make the necessary changes on the SQLs queries to accomplish the same thing.
After that, go to your test class and add the #TestExecutionListeners annotation, like:
#TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {ResetDatabaseTestExecutionListener.class}
)
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CreateOrderIT {
This should work.
If you not see any performance difference between this approach and #DirtiesContext, probably you are using #MockBean in your tests, what marks the Spring context as dirty and automatically reload the entire context.
With spring boot the h2 database can be defined uniquely for each test. Just override the data source URL for each test
#SpringBootTest(properties = {"spring.config.name=myapp-test-h2","myapp.trx.datasource.url=jdbc:h2:mem:trxServiceStatus"})
The tests can run in parallel.
Within the test the data can be reset by
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
There is library that covers "reset H2 database" feature in JUnit 5 tests:
https://github.com/cronn/test-utils#h2util
Sample usage:
#ExtendWith(SpringExtension.class)
#Import(H2Util.class)
class MyTest {
#BeforeEach
void resetDatabase(#Autowired H2Util h2Util) {
h2Util.resetDatabase();
}
// tests...
}
Maven coords:
<dependency>
<groupId>de.cronn</groupId>
<artifactId>test-utils</artifactId>
<version>0.2.0</version>
<scope>test</scope>
</dependency>
Disclaimer: I’m the author of suggested library.
If you use spring.jpa.hibernate.ddl-auto=create-drop should be enough to create/drop database?
Unless you're using some kind of Spring-Data integration (which I don't know at all), this seems like custom logic you'll need to implement yourself. Spring doesn't know about your databases, its schemas, and tables.
Assuming JUnit, write appropriate #Before and #After methods to set up and clean up your database, its tables, and data. Your tests can themselves write the data they need, and potentially clean up after themselves if appropriate.
A solution using try/resources and a configurable schema based on this answer. Our trouble was that our H2 database leaked data between test cases. So this Listener fires before each test method.
The Listener:
public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {
private static final List<String> IGNORED_TABLES = List.of(
"TABLE_A",
"TABLE_B"
);
private static final String SQL_DISABLE_REFERENTIAL_INTEGRITY = "SET REFERENTIAL_INTEGRITY FALSE";
private static final String SQL_ENABLE_REFERENTIAL_INTEGRITY = "SET REFERENTIAL_INTEGRITY TRUE";
private static final String SQL_FIND_TABLE_NAMES = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='%s'";
private static final String SQL_TRUNCATE_TABLE = "TRUNCATE TABLE %s.%s";
private static final String SQL_FIND_SEQUENCE_NAMES = "SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='%s'";
private static final String SQL_RESTART_SEQUENCE = "ALTER SEQUENCE %s.%s RESTART WITH 1";
#Autowired
private DataSource dataSource;
#Value("${schema.property}")
private String schema;
#Override
public void beforeTestClass(TestContext testContext) {
testContext.getApplicationContext()
.getAutowireCapableBeanFactory()
.autowireBean(this);
}
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
cleanupDatabase();
}
private void cleanupDatabase() throws SQLException {
try (
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()
) {
statement.execute(SQL_DISABLE_REFERENTIAL_INTEGRITY);
Set<String> tables = new HashSet<>();
try (ResultSet resultSet = statement.executeQuery(String.format(SQL_FIND_TABLE_NAMES, schema))) {
while (resultSet.next()) {
tables.add(resultSet.getString(1));
}
}
for (String table : tables) {
if (!IGNORED_TABLES.contains(table)) {
statement.executeUpdate(String.format(SQL_TRUNCATE_TABLE, schema, table));
}
}
Set<String> sequences = new HashSet<>();
try (ResultSet resultSet = statement.executeQuery(String.format(SQL_FIND_SEQUENCE_NAMES, schema))) {
while (resultSet.next()) {
sequences.add(resultSet.getString(1));
}
}
for (String sequence : sequences) {
statement.executeUpdate(String.format(SQL_RESTART_SEQUENCE, schema, sequence));
}
statement.execute(SQL_ENABLE_REFERENTIAL_INTEGRITY);
}
}
}
Using a custom annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = { ResetDatabaseTestExecutionListener.class }
)
public #interface ResetDatabase {
}
You can easily mark each test in which you want to reset the database:
#SpringBootTest(
webEnvironment = RANDOM_PORT,
classes = { Application.class }
)
#ResetDatabase
public class SomeClassIT {
You can annotate your test class with #Transactional:
import org.springframework.transaction.annotation.Transactional;
...
...
#RunWith(SpringRunner.class)
#Transactional
public class MyClassTest {
#Autowired
private SomeRepository repository;
#Before
public void init() {
// add some test data, that data would be rolled back, and recreated for each separate test
repository.save(...);
}
#Test
public void testSomething() {
// add some more data
repository.save(...);
// update some base data
repository.delete(...);
// all the changes on database done in that test would be rolled back after test finish
}
}
All tests are wrapped inside a transaction, that is rolled back at the end of each test. There are unfortunately some problems with that annotation of course, and you need to pay special attention, when for example your production code uses transactions with different score.
You could also try out https://www.testcontainers.org/ which helps you to run databases inside containers and you can create a fresh database for each test run too. It will be very slow though, since each time a container has to be created and the database server has to be started, configured and then migrations have to be run, then the test can be executed.
Nothing worked for me, but the following:
For every test class you can put the following annotations:
#TestMethodOrder(MethodOrderer.OrderAnnotation.class) //in case you need tests to be in specific order
#DataJpaTest // will disable full auto-configuration and instead apply only configuration relevant to JPA tests
#AutoConfigureTestDatabase(replace = NONE) //configures a test database to use instead of the application-defined or auto-configured DataSource
To order specific tests within the class you have to put also #Order annotation:
#Test
#Order(1) //first test
#Test
#Order(2) //second test, etc.
Rerunning the tests will not fail because of previous manipulations with db.
tl;dr;. I have a method for creating new database table on the fly, and I want to write a unit test for it. Unfortunately, test runner does not perform rollback after tests in a proper way, and the table still remains in the DB after tests finished. What should I do?
Long story:
I am not very familiar neither with Java Persistence nor with Java Spring, so, if you found current solution ugly (as for me, it is rather ugly), please, tell me how to improve it - I will very appreciate your opinion.
I have a SysDatastoreService with the following implementation of addStaticDatastore method.
#Service
public class SysDatastoreServiceImpl implements SysDatastoreService {
#Autowired
private SysDatastoreRepository datastoreRepository;
#Autowired
private DataSource dataSource;
#Override
#Transactional
public Optional<SysDatastore> addStaticDatastore(String name, String tableName, String ident, Long ord) {
String createTableSql = PostgresTableSqlBuilder.createTableInPublicSchemaWithBigintPkAndFkId(
tableName,
SysObject.TABLE_NAME,
Optional.of(SysObject.ID_COLUMN_NAME)).buildSql();
Optional<SysDatastore> sysDatastore = Optional.empty();
try(
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()
) {
connection.setAutoCommit(false);
Savepoint beforeTableCreation = connection.setSavepoint();
try {
statement.execute(createTableSql);
sysDatastore = Optional.ofNullable(
datastoreRepository.save(new SysDatastore(name, tableName, DatastoreType.STATIC, ident, ord)));
} catch(SQLException e) {
e.printStackTrace();
}
if(!sysDatastore.isPresent()) {
connection.rollback(beforeTableCreation);
} else {
connection.commit();
}
} catch(SQLException e1) {
e1.printStackTrace();
}
return sysDatastore;
}
}
So, as you can see, I receive new connection from DataSource and try to create new table. In success, I will create a new entry in the SysDataStoreRepository, and, if this fails, I will perform a rollback for the table creation.
There are some disadvantages of current approach, one of them is that table creation and entry insertion operates on separate connections (am I right?).
But I have some problem while writing a unit test. This is what I tried:
#Transactional(propagation = Propagation.REQUIRED)
#RunWith(SpringJUnit4ClassRunner.class)
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
#ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/rest-servlet.xml")
public class SysDatastoreServiceTest {
#Autowired
private SysDatastoreService sysDatastoreService;
#Autowired
private DataSource dataSource;
#Test
public void testAddStaticDatastore() throws Exception {
Optional<SysDatastore> sysDatastore =
sysDatastoreService.addStaticDatastore("New static datastore", "new_datastore_table",
"NEW_DATASTORE_TABLE", 42L);
assertTrue(sysDatastore.isPresent());
assertEquals("New static datastore", sysDatastore.get().getName());
assertEquals("NEW_DATASTORE_TABLE", sysDatastore.get().getIdent());
assertEquals("new_datastore_table", sysDatastore.get().getTableName());
assertEquals(DatastoreType.STATIC, sysDatastore.get().getDynType());
assertEquals(42L, sysDatastore.get().getOrd().longValue());
assertTrue(dataSource.getConnection()
.getMetaData()
.getTables(null, null, sysDatastore.get().getTableName(), null)
.next());
}
This test seems pretty easy: I just compare all the fields, and then checks database for a new table.
However, this test fails when I run it twice or more times. Looking at the database I noticed, that the table new_datastore_table still remained in the schema. I guess, it was not rollbacked properly because of hand-written transaction and raw sql execution, but I am not sure.
Question: How should I write a test case for this method in a proper way? And, in case if the current approach is fundamentally wrong, how it should be changed?
Side notes: I use PostgreSQL database, and it cannot be replaced with non-relational database.
First a CREATE TABLE is a DDL statement, not a DML one. That means that a rollback will not delete the table. If you want to clean your database, you must explicitely remove it in a #After or #AfterClass method.
But do you really need to do the tests on the PostgreSQL database? Spring has great support for in memory databases and the default embedded database is HSQL which has a pretty good support for postgresql syntax. Provided you have no complex statement, it could be enough and avoids cluttering the main database for (potentially destructive) unit tests.
You could create the database in a #BeforeClass method. Here is an oversimplified example:
private static DriverManagerDataSource dataSource;
#BeforeClass
public static void setupClass() throws Exception {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(new ClassPathResource("path/to/package/defaults.sql"));
dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:hsqldb:mem:pgtest;sql.syntax_pgs=true");
dataSource.setUsername("SA");
Connection con = dataSource.getConnection();
assertNotNull(con);
populator.populate(con);
con.close();
}
I would like to know what's the best way to register a custom SQL function with JPA/Hibernate.
Do I have to go through extending the MysqlInnodb dialect or is there a better way?
Can anyone please provide code samples and pointers to relevant documentation?
You might read articles telling you to register the SQL function by extending the Hibernate Dialect, but that's a naive solution.
Since Hibernate ORM 5.2.18 and 5.3.1, the best way to register a SQL function is to supply a MetadataBuilderContributor like this:
public class SqlFunctionsMetadataBuilderContributor
implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction(
"group_concat",
new StandardSQLFunction(
"group_concat",
StandardBasicTypes.STRING
)
);
}
}
Which you can pass to Hibernate via the hibernate.metadata_builder_contributor configuration property:
<property>
name="hibernate.metadata_builder_contributor"
value="com.vladmihalcea.book.hpjp.hibernate.query.function.SqlFunctionsMetadataBuilderContributor"
</property>
Or, if you bootstrap Hibernate natively, you can just apply the SQL function to the MetadataBuilder during bootstrap.
Yes extending the dialect is a good way of registering custom SQL function.
Add something like this in your Dialect classes constructor.
registerFunction("current_timestamp", new NoArgSQLFunction(Hibernate.TIMESTAMP) );
registerFunction("date", new StandardSQLFunction(Hibernate.DATE) );
Look at the source code of one of the existing dialect classes.
http://www.koders.com/java/fid0E7F787E2EC52F1DA8DFD264EDFBD2DE904A0927.aspx
Register SQL Method every version
//Add Hibernate Properties
properties.put("hibernate.dialect",
"com.sparkslink.web.config.sql.RegisterSqlFunction");
//Create A Class
public class RegisterSqlFunction extends MySQLDialect {
public RegisterSqlFunction() {
super();
registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
}
}
//Dao Method
public List<Client> getTest() {
Query query = getSession()
.createQuery("SELECT sl.name as name ,group_concat(sl.domain) as domain FROM SlClient sl GROUP BY sl.name");
query.setResultTransformer(Transformers.aliasToBean(Client.class));
return query.list();
}
//DTO Class
public class Client {
private String name;
private String domain;
//Getter
//Setter
}
I am using Hibernate + JPA as my ORM solution.
I am using HSQL for unit testing and PostgreSQL as the real database.
I want to be able to use Postgres's native UUID type with Hibernate, and use the UUID in its String representation with HSQL for unit testing (since HSQL does not have a UUID type).
I am using a persistence XML with different configurations for Postgres and HSQL Unit Testing.
Here is how I have Hibernate "see" my custom UserType:
#Id
#Column(name="UUID", length=36)
#org.hibernate.annotations.Type(type="com.xxx.UUIDStringType")
public UUID getUUID() {
return uuid;
}
public void setUUID(UUID uuid) {
this.uuid = uuid;
}
and that works great. But what I need is the ability to swap out the "com.xxx.UUIDStringType" part of the annotation in XML or from a properties file that can be changed without re-compiling.
Any ideas?
Hy, for those who are seeking for a solution in Hibernate 4 (because the Dialect#addTypeOverride method is no more available), I've found one, underlying on this Steve Ebersole's comment
You have to build a custom user type like this one :
public class UUIDStringCustomType extends AbstractSingleColumnStandardBasicType {
public UUIDStringCustomType() {
super(VarcharTypeDescriptor.INSTANCE, UUIDTypeDescriptor.INSTANCE);
}
#Override
public String getName() {
return "pg-uuid";
}
}
And to bind it to the HSQLDB dialect, you must build a custom dialect that override the Dialect#contributeTypes method like this :
public class CustomHsqlDialect extends HSQLDialect {
#Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes(typeContributions,serviceRegistry);
typeContributions.contributeType(new UUIDStringCustomType());
}
}
Then you can use the #Type(type="pg-uuid") with the two databases.
Hope it will help someone...
This question is really old and has been answered for a long time, but I recently found myself in this same situation and found a good solution. For starters, I discovered that Hibernate has three different built-in UUID type implementations:
binary-uuid : stores the UUID as binary
uuid-char : stores the UUID as a character sequence
pg-uuid : uses the native Postgres UUID type
These types are registered by default and can be specified for a given field with a #Type annotation, e.g.
#Column
#Type(type = "pg-uuid")
private UUID myUuidField;
There's also a mechanism for overriding default types in the Dialect. So if the final deployment is to talk to a Postgres database, but the unit tests use HSQL, you can override the pg-uuid type to read/write character data by writing a custom dialect like so:
public class CustomHSQLDialect extends HSQLDialect {
public CustomHSQLDialect() {
super();
// overrides the default implementation of "pg-uuid" to replace it
// with varchar-based storage.
addTypeOverride(new UUIDCharType() {
#Override
public String getName() {
return "pg-uuid";
}
});
}
}
Now just plug in the custom dialect, and the the pg-uuid type is available in both environments.
To avoid problems between the UUID types without specifying the #Type annotation (which basically means you have to adjust all annotations when you want to change from postgres to mysql or the other way around...) I'm using a package-info.java with the hibernates #TypeDef annotation on that package.
Here's an example setup of your application:
Assuming module/src/main/java/app.package.domain contains your entities. And you'r tests are stored in module/src/test/java/app.package.
Simply create two package-info.java in your domain packages.
Make sure the package-info files are always in the same package (for testing and production). See the following example below:
src/main/java
app
package
domain
package-info.java
src/test/java
app
package
domain
package-info.java
The content of you're production package-info.java should look like this (Postgres):
#TypeDef(
name = "pg-uuid",
defaultForType = UUID.class,
typeClass = PostgresUUIDType.class
)
package app.package.domain;
import org.hibernate.annotations.TypeDef;
import org.hibernate.type.PostgresUUIDType;
import java.util.UUID;
And this is how you'r testing "configuration" should look like (H2):
#TypeDef(
name = "uuid-char",
defaultForType = UUID.class,
typeClass = UUIDCharType.class
)
package app.package.domain;
import org.hibernate.annotations.TypeDef;
import org.hibernate.type.UUIDCharType;
import java.util.UUID;
Hope it helps
Perhaps you can build some smarts in your user type to do the right thing depending on the database capabilities. Hibernate itself takes a similar approach with its "native" ID generator, which behaves differently depending on the type of database you are using. An approach like this eliminates the need to switch the mapping at runtime.
For example, you could create one strategy class for each database. Then in your user type class, detect what database you're connected to when you're called for the first time, instantiate the proper strategy for that database, and then delegate all calls to the strategy object.
This answer is based on the other answers to this question and works with Hibernate 4.
Solution 1 for column definition with #Type(type = "org.hibernate.type.PostgresUUIDType")
Use this HSQL dialect:
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.PostgresUUIDType;
import org.hibernate.type.descriptor.java.UUIDTypeDescriptor;
import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor;
public class UuidHsqlDialect extends HSQLDialect {
#Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes(typeContributions, serviceRegistry);
typeContributions.contributeType(new AbstractSingleColumnStandardBasicType(VarcharTypeDescriptor.INSTANCE, UUIDTypeDescriptor.INSTANCE) {
#Override
public String getName() {
return PostgresUUIDType.class.getName();
}
});
}
}
Solution 2 for column definition with #Type(type = "pg-uuid")
Use this HSQL dialect:
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.descriptor.java.UUIDTypeDescriptor;
import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor;
public class UuidHsqlDialect extends HSQLDialect {
#Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes(typeContributions, serviceRegistry);
typeContributions.contributeType(new AbstractSingleColumnStandardBasicType(VarcharTypeDescriptor.INSTANCE, UUIDTypeDescriptor.INSTANCE) {
#Override
public String getName() {
return PostgresUUIDType.class.getName();
}
});
}
}
JPA configuration
Reference your new UuidHsqlDialect class in your (test) application.properties:
spring.jpa.properties.hibernate.dialect=<full-package-name>.UuidHsqlDialect