How to select InnoDB or XtraDB as storage engine in MariaDB in Spring Boot 2 JPA application - spring

I am developing a new application using Spring Boot 2.0.0.M6 and Spring Data JPA. I am using MariaDB v10.
Below is my dev properties file.
spring.jpa.hibernate.ddl-auto=create-drop
spring.datasource.url=jdbc:mariadb://localhost:3306/testdb
spring.datasource.username=user
spring.datasource.password=
spring.jpa.show-sql=true
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
org.hibernate.dialect.Dialect=MariaDB53Dialect
spring.jooq.sql-dialect=MariaDB53Dialect
I get output:
Hibernate: create table hibernate_sequence (next_val bigint) engine=MyISAM
I am not able to change the storage engine. All the tables are being created using storage engine MyISAM.
I am able to create tables manually using other storage engines. But for some reason Spring or Hibernate falls back to MyISAM engine only.
With pure Hibernate-Java application, Hibernate uses InnoDB as default.
INFO: HHH000412: Hibernate Core {5.2.11.Final}
Hibernate: create table hibernate_sequence (next_val bigint) engine=InnoDB
Is there any way to override Database storage engine from the Spring Boot properties?

As described in Spring Boot's documentation, all properties prefixed with spring.jpa.properties are passed through to the underlying JPA provider (Hibernate in this case) with the prefix removed.
The Hibernate property to configure the dialect is hibernate.dialect and its value should be the fully qualified class name of the dialect that you want to use. In this case that's org.hibernate.dialect.MariaDB53Dialect.
Putting the above together, you could set the following property in your application.properties:
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDB53Dialect
With this in place your Spring Boot-based application uses the MariaDB dialect:
2017-11-09 14:18:17.557 INFO 69955 --- [ost-startStop-1] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MariaDB53Dialect

With Hibernate 5.2.12, if I run the MySQLStoredProcedureTest while setting the dialect to MariaDB:
#RequiresDialect(MariaDB53Dialect.class)
public class MySQLStoredProcedureTest
extends BaseEntityManagerFunctionalTestCase {
#Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Phone.class,
};
}
...
}
The Post entity is mapped as follows:
#Entity
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
private String nickName;
private String address;
#Temporal(TemporalType.TIMESTAMP )
private Date createdOn;
#OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
#OrderColumn(name = "order_id")
private List<Phone> phones = new ArrayList<>();
#Version
private int version;
//Getters and setter omitted for brevity
}
And, when I run the test on MariaDB, Hibernate generates the following schema:
create table Person (
id bigint not null,
address varchar(255),
createdOn datetime(6),
name varchar(255),
nickName varchar(255),
version integer not null,
primary key (id)
) engine=InnoDB
That's because MariaDB53Dialect extends the MariaDBDialect which uses the InnoDBStorageEngine:
public class MariaDBDialect extends MySQL5Dialect {
public MariaDBDialect() {
super();
}
public boolean supportsRowValueConstructorSyntaxInInList() {
return true;
}
#Override
protected MySQLStorageEngine getDefaultMySQLStorageEngine() {
return InnoDBStorageEngine.INSTANCE;
}
}
So, it's impossible to get MyISAM with MariaDB53Dialect when generating the schema with hbm2ddl.
However, you should only be using hbm2ddl to generate the initial script. In a production environment, you should use a tool like FlywayDB.
We actually wrote this in the Hibernate User Guide:
Although the automatic schema generation is very useful for testing
and prototyping purposes, in a production environment, it’s much more
flexible to manage the schema using incremental migration scripts.
We changed this in Hibernate 5.2.8, so I suppose you are using an older version instead, otherwise, there is no explanation why you'd see MyISAM in your hbm2ddl auto-generated schema.
Check the dependencies using:
mvn dependency:tree
and make sure you are really using Hibernate 5.2.12.

Related

Spring Boot 2.7.1 with Data R2DBC findById() fails when using H2 with MSSQLSERVER compatibility (bad grammar LIMIT 2)

I'm upgrading a Spring Boot 2.6.9 application to the 2.7.x line (2.7.1). The application tests use H2 with MS SQL Server compatibility mode.
I've created a simple sample project to reproduce this issue: https://github.com/codependent/boot-h2
Branches:
main: Spring Boot 2.7.1 - Tests KO
boot26: Spring Boot 2.6.9 - Tests OK
To check the behaviour just run ./mvnw clean test
These are the relevant parts of the code:
Test application.yml
spring:
r2dbc:
url: r2dbc:h2:mem:///testdb?options=MODE=MSSQLServer;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
schema.sql
CREATE SCHEMA IF NOT EXISTS [dbo];
CREATE TABLE IF NOT EXISTS [dbo].[CUSTOMER] (
id INTEGER GENERATED BY DEFAULT AS IDENTITY,
name VARCHAR(255) NOT NULL,
CONSTRAINT PK__otp__D444C58FB26C6D28 PRIMARY KEY (id)
);
Entity
#Table("[dbo].[CUSTOMER]")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class CustomerEntity {
#Id
#Column("id")
private Integer id;
#Column("name")
private String name;
}
Data R2DBC Repository
public interface CustomerRepository extends ReactiveCrudRepository<CustomerEntity, Integer> {
}
The problem occurs when invoking customerRepository.findById(xxx), as can be seen in the following test
#SpringBootTest
#RequiredArgsConstructor
#TestConstructor(autowireMode = ALL)
class BootH2ApplicationTests {
private final CustomerRepository customerRepository;
#Test
void shouldSaveAndLoadUsers() {
CustomerEntity joe = customerRepository.save(new CustomerEntity(null, "Joe")).block();
customerRepository.findById(joe.getId()).block();
Exception:
Caused by: io.r2dbc.spi.R2dbcBadGrammarException:
Syntax error in SQL statement "SELECT [dbo].[CUSTOMER].* FROM [dbo].[CUSTOMER] WHERE [dbo].[CUSTOMER].id = $1 [*]LIMIT 2"; SQL statement:
SELECT [dbo].[CUSTOMER].* FROM [dbo].[CUSTOMER] WHERE [dbo].[CUSTOMER].id = $1 LIMIT 2 [42000-214]
The R2dbcEntityTemplate is limiting the selectOne query to 2 elements:
public <T> Mono<T> selectOne(Query query, Class<T> entityClass) throws DataAccessException {
return (Mono)this.doSelect(query.getLimit() != -1 ? query : query.limit(2), entityClass, this.getTableName(entityClass), entityClass, RowsFetchSpec::one);
}
And this is translated into a LIMIT N clause which is not supported by H2/SQL Server.
Not sure if it's some kind of H2/Spring Data bug or there's a way to fix this.
It will be solved in Spring Data 2.4.3 (2021.2.3)

Import data at startup Spring boot

I'm trying to launch a SQL file at my database initialization.
Here is my configuration:
spring:
profiles: local
jpa:
properties:
hibernate.temp.use_jdbc_metadata_defaults: false
generate-ddl: true
hibernate:
ddl-auto: update
database: h2
show-sql: true
autoCommit: false
datasource:
platform: h2
url: jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;
driver-class-name: org.h2.Driver
initialization-mode: always
data: classpath:/sql/CreateGeographicZones.sql
My script is just this line (atm):
INSERT INTO GEOGRAPHIC_ZONE (name) VALUES ('EUROPE');
And the related entity:
#NoArgsConstructor
#Data
#Entity
#Table(name = "GEOGRAPHIC_ZONE")
public class GeographicZone {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "geo_zone_sequence")
#SequenceGenerator(name = "geo_zone_sequence", sequenceName = "geo_zone_id_seq", allocationSize = 1)
#Column(nullable = false)
private Long id;
...
}
The table is created as I can see in the logs:
Hibernate: create table geographic_zone (id bigint not null, name varchar(100) not null, primary key (id))
But I have an SQL error when the script is executed:
Table "GEOGRAPHIC_ZONE" not found; SQL statement:
INSERT INTO GEOGRAPHIC_ZONE (name) VALUES ('EUROPE')
In the logs I can see that my table is created before the script execution, so why it's not working ?
According with your entity's metadata Hibernate is querying geo_zone_id_seq sequence's next value and using it for the ID on each insert.
If you would like to use the same approach when inserting directly in your database then you will need to implement a H2 Trigger
Also you may use either the EntityManager bean or your Spring JPA Repository to insert your data after application startup via CommandLineRunner interface.
Using EntityManager:
#Bean
CommandLineRunner registerZonesDataRunner(EntityManager entityManager, TransactionTemplate transactionTemplate) {
return args -> transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// presuming that GeographicZone has a constructor expecting NAME
Stream.of("AFRICA", "EUROPE")
.map(GeographicZone::new)
.forEach(entityManager::persist);
}
});
Using Spring JPA Repository:
#Bean
CommandLineRunner registerZonesDataRunner(GeographicZoneRepository repository) {
// presuming that GeographicZone has a constructor expecting NAME
return args -> repository.saveAll(Stream.of("AFRICA", "EUROPE")
.map(GeographicZone::new)
.collector(Collectors.toList()));
}
minimal, reproducible example
You don't show how you've defined the id column but the schema indicates there is no auto-generation scheme. So, try:
INSERT INTO GEOGRAPHIC_ZONE (id, name) VALUES (1, 'EUROPE');
in your data file. If that works, you'll need to either manually set the id in your inserts or add something like #GeneratedValue(strategy = AUTO) to your #Id property.

The stored procedure call with cursors throws invalid column name exception

We have a Spring Boot application where we need to connect to Oracle DB and fetch data via stored procedures. Each of our stored procedure has REF_CURSOR as OUT parameters. I am trying the same using #NamedStoredProcedureQuery and #Entity annotations. We are using ojdbc14.jar in pom.xml and Oracle12cDialect in application.properties file. I get the exception Invalid Column Name while executing my piece of code. Also in the entity class I had to introduce a field with annotation #Id, although there is no such field being returned by the REF_CURSOR of my stored procedure. Can this be a problem? Also not defining #Id field is not an option since Hibernate throws an exception then. Any hints would be highly appreciated.
Implementation and Problem is very similar to the question
Invalid column name exception when calling an Oracle stored procedure with ref_cursor through JPA 2.1
But no answer is posted there
The simple example how you can achieve it:
The database schema.
create table MY_PATIENT
(
PAT_RECID number,
PAT_NAME varchar2(100),
constraint PAT_PK primary key(PAT_RECID)
);
create table MY_ORDER
(
ORD_RECID number,
ORD_CODE varchar2(15),
ORD_PATID number,
constraint ORD_PK primary key(ORD_RECID),
constraint ORD_PAT_FK foreign key(ORD_PATID) references MY_PATIENT(PAT_RECID),
constraint ORD_CODE_UNIQUE unique (ORD_CODE)
);
CREATE OR REPLACE PROCEDURE fetch_patient_orders(
patientId IN NUMBER,
patientOrders OUT SYS_REFCURSOR)
AS
BEGIN
OPEN patientOrders FOR
SELECT *
FROM MY_ORDER
WHERE ORD_PATID = patientId;
END;
The entity definition.
#NamedStoredProcedureQueries(
#NamedStoredProcedureQuery(
name = "fetch_patient_orders",
procedureName = "fetch_patient_orders",
resultClasses = Order.class,
parameters = {
#StoredProcedureParameter(
name = "patientId",
type = Long.class,
mode = ParameterMode.IN
),
#StoredProcedureParameter(
name = "patientOrders",
type = Class.class,
mode = ParameterMode.REF_CURSOR
)
}
)
)
#Entity
#Table(name = "MY_ORDER")
public class Order
{
#Id
#Column(name = "ORD_RECID")
private Long id;
#Column(name = "ORD_CODE")
private String code;
#ManyToOne
#JoinColumn(name = "ORD_PATID")
private Patient patient;
}
And usage:
List<Order> orders = session.createNamedStoredProcedureQuery("fetch_patient_orders")
.setParameter("patientId", 2L)
.getResultList();
It was tested with hibernate 5.4.12.Final, ojdbc8.jar, Oracle12cDialect.
See also the hibernate documentation.
The described above approach will work in a pure hibernate application, but not in spring boot app.
According to the spring boot documentation:
Connection to a Production Database
Production database connections can also be auto-configured by using a pooling DataSource. Spring Boot uses the following algorithm for choosing a specific implementation:
We prefer HikariCP for its performance and concurrency. If HikariCP is available, we always choose it.
Otherwise, if the Tomcat pooling DataSource is available, we use it.
If neither HikariCP nor the Tomcat pooling datasource are available and if Commons DBCP2 is available, we use it.
If you use the spring-boot-starter-jdbc or spring-boot-starter-data-jpa “starters”, you automatically get a dependency to HikariCP.
You can bypass that algorithm completely and specify the connection pool to use by setting the spring.datasource.type property.
So, spring boot uses HikariCP JDBC connection pool by default. And it looks like it has a problem with REF_CURSOR parameter registering:
o.h.r.j.i.ResourceRegistryStandardImpl : Registering statement [HikariProxyCallableStatement#770201936 wrapping oracle.jdbc.driver.OracleCallableStatementWrapper#528a6369]
o.h.type.descriptor.sql.BasicBinder : binding parameter [patientId] as [BIGINT] - [2]
o.h.s.i.AbstractServiceRegistryImpl : Initializing service [role=org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport]
o.h.engine.jdbc.spi.SqlExceptionHelper : Error registering REF_CURSOR parameter [patientOrders] [n/a]
When I use the oracle specific data source pool in the application.properties:
# com.zaxxer.hikari.HikariDataSource (default value)
spring.datasource.type=oracle.jdbc.pool.OracleDataSource
all work fine.

Sequence "HIBERNATE_SEQUENCE" not found; SQL statement

In my spring mvc app, i have the following object. I am trying to make a visual of data using devtool in my app.
#Entity
#Data
public class ConsultationRequest {
#Id
#GeneratedValue
private Long id;
private String name;
private String email;
private String purpose;
private String programme;
private int year;
private String language;
private String comments;
#Enumerated(EnumType.STRING)
private ConsultationStatus status;
}
Then i used the jpa to make the entity:
#Repository
public interface ConsultationRequestRepository extends JpaRepository<ConsultationRequest, Long> {
}
The problem is when i load my application, i face with 2 errors:
Unsuccessful: drop sequence hibernate_sequence
[36morg.hibernate.tool.hbm2ddl.SchemaExport Sequence "HIBERNATE_SEQUENCE" not found; SQL statement:
Then when i open the
http://localhost:8080/h2-console/
I cannot see the table.
It seems that the in the boot process, table is not made.
Update your code as below:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
As you have not specified a sequence table name, hibernate will look for a sequence table named as hibernate_sequence and use it as default.
For Oracle/Postgres, increment fields used are sequence tables.
In MySql, there are increment fields that automatically increment.
If someone is getting this error with Spring Boot testing(with H2) , we have to use following in the application.properties(or whatever profile we are using) :
spring.jpa.hibernate.ddl-auto=create-drop
Setting the following property in the properties file helped me solve the hibernate_sequence problem for hibernate 5.4v
spring:
jpa:
hibernate:
use-new-id-generator-mappings: false
Check persistence.xml
property name="hibernate.hbm2ddl.auto" value="create"
not hdm2ddl
This worked in my case.
SQL operation ERROR when start app spring-boot.
I added the setting in spring properties and solved: in the spring:jpa.hibernate.ddl-auto= create-drop to hibernate be able create or drop table automatically.
If you use a 2nd cache with liquidbase, you have to add the sequence in the changelog like this:
<changeSet author="liquibase-docs"
id="createSequence-example">
<createSequence catalogName="cat" cycle="false"
incrementBy="1" ordered="true" schemaName="public"
sequenceName="hibernate_sequence" startValue="0" />
</changeSet>
For spring-boot 2.7.x and h2 2.x you need to add MODE=LEGACY; in the database connection:
example application.yml:
spring:
datasource:
url: jdbc:h2:mem:test;MODE=LEGACY;
exemple application.properties:
spring.datasource.url=jdbc:h2:mem:test;MODE=LEGACY;
For Mysql:
the auto-increment is not added, modify ur table:
ALTER TABLE table_name MODIFY COLUMN id BIGINT AUTO_INCREMENT=1

Unable to see generated tables generated by hibernate schema export (H2 database)

I am trying to get a small app going using Spring Boot (v1.1.1.RELEASE), and H2 database. In the logging i see that the ddl is correctly generated but it i just cannot find the tables inside the H2 database.
I manually copied the ddl into a db visualizer and the sql is ok. I have no clue what i am missing here. When executing code the JPA persistence layer seems to store the data correctly as i get generated ID's back etc.. I was thinking that i made a mistake in the jdbc url, but they all point to the same file based H2 database. But this database just seems to hold no data.
The JPA object
#Entity
#Table(name = "rawdata", schema = "PUBLIC")
public class RawData {
#Id
#GeneratedValue
private Long id;
#Lob
#Column(name = "payload", nullable = false)
private String payload;
// getters and setters omitted
}
The JPARepository
#Repository
public interface RawDataRepository extends JpaRepository<RawData, Long> {
}
Application properties
spring.datasource.url=jdbc:h2:file:/home/username/dev-db
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
Logging info
org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl schema export
Hibernate: drop table PUBLIC.rawdata if exists
Hibernate: create table PUBLIC.rawdata (id bigint generated by default as identity, payload clob not null, primary key (id))
org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export complete
Test code
#Autowired
private RawDataRepository repository;
repository.saveAndFlush(new RawData("test"));
System.out.println(repository.count());
So saving a JPA object actually seems to persist the object (the count increases etc) but the data and table structure do not appear in the database. I see that the modified date changes of the database when persisting an object but i seem unable to view the data with for example squirrel/dbvisualizer etc.. Any hints or tips?
The problem is that when the application is shutdown, Hibernate will drop the entire schema, because you have configured spring.jpa.hibernate.ddl-auto=create-drop.
If you change your configuration to spring.jpa.hibernate.ddl-auto=create the schema will not be dropped at the end of the session and you will be able to see the tables that where created along with any data you inserted

Resources