Showing posts with label h2. Show all posts
Showing posts with label h2. Show all posts

Tuesday, September 18, 2018

How to use “on update CURRENT_TIMESTAMP” in H2 database?

Leave a Comment

I want my entity to have a modification timestamp whenever it is updated. mysql supports this using the following definition:

@Entity public class MyTable {     @Column(columnDefinition = "TIMESTAMP default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP")     private LocalDateTime thetime; } 

Problem: in my JUnit tests I want to use an embedded inmemory H2 database. And H2 does not support on update CURRENT_TIMESTAMP.

Question: how can I keep the column definition (as I will be running mysql in all cases except in automated tests)? And how can I workaround that in my h2 testing?

1 Answers

Answers 1

The official statement from the H2 people is that it is not supported and the workaround is to create a trigger. You can read this here https://github.com/commandos59/h2database/issues/491

Whatever you put in the "columnDefinition" it is provider specific. And since you have already mapped your entity with this specific column definition you are not leaving yourself much space to manouver.

There are several things you can do. Some of the things are hacks.

  1. Mix XML configuration for the tests. The XML configurationn of the Entities has higher priority than the annotations so you can actualy override the

    @Column(columnDefinition = "TIMESTAMP default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP") private LocalDateTime thetime

with H2 specific column definition.

  1. Another alternative agnostic of the Database is to leave your time generation to the application server layer and hook it to the @PrePersist @PreUpdate listeners on the entity

  2. If you insist to have your timestamp generated by the database you can do something similar to how the IDs are generated. Have some sort of dedicated object that reads the CURRENT_TIMESTAMP from the database and puts it the entty right before you persist, update.

Read More

Thursday, January 11, 2018

Deleting LOBs does not decrease Hibernate H2 DB size

Leave a Comment

I am using H2 1.4.196. I have a Payload table that holds LOBs. I trigger a deletion of the entities within the table through a Java program, and verify via the H2 Console that the table is now empty.

However, the db.mv file does not decrease in size. Repeated creation and deletion of LOBs leaves the table empty, but the db.mv continues to grow indefinitely, and looking into the file I still see the LOB contents. Only upon DROP TABLE Payload does the size of the db.mv file actually decrease.

I had a theory that it could be an open transaction, but SELECT * FROM INFORMATION_SCHEMA.SESSIONS only showed the session created by the sessions sql statement.

What could be causing this inability to truly delete the LOBs?

1 Answers

Answers 1

Update: I found https://github.com/h2database/h2database/issues/681, and building from source to include the Nov 29 commit resulted in the DB file to successfully decrease. However, it took about 20 seconds for the deletion to propagate and reflect in the file size, and insertions within this timeframe resulted in the deletion never completing.

Similar issues have been around for years: https://groups.google.com/forum/#!topic/h2-database/CGXOfSx_Vq4

According to http://h2database.com/html/features.html#compacting, “Empty space in the database file re-used automatically. When closing the database, the database is automatically compacted for up to 200 milliseconds by default.” Haven’t really seen this to be the case with our LOBs as the DB grows every iteration of the rest mon task.

It suggests a manual SHUTDOWN COMPACT as a workaround to compact more

https://groups.google.com/forum/#!topic/h2-database/eXBzpF4WnNk: “Please note the database file doesn’t shrink if you delete data (but keep the database open). However, empty space within the file is automatically re-used. The database file only ever shrinks if you close the database (close all connections or run “shutdown”).”

Read More

Tuesday, December 5, 2017

Tests that use h2 in-mem db fail on Heroku

Leave a Comment

My Spring boot app has some tests that pass fine on my local, but fail on Heroku:

org.h2.jdbc.JdbcSQLException: Exception opening port "8082" (port may be in use), cause: "java.net.BindException: Address already in use (Bind failed)" [90061-196]

The data source configuration for the test profile:

@Configuration public class TestDataSourceConfiguration {     @Bean     @ConfigurationProperties(prefix = "spring.datasource")     @Profile("test")     public DataSource testDataSource() throws URISyntaxException {         return DataSourceBuilder.create().build();     } } 

application-test.properties: spring.datasource.url=jdbc:h2:mem:tesdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=

spring.datasource.testWhileIdle = true spring.datasource.validationQuery = SELECT 1 

I know Heroku doesn't support h2, but this shouldn't be the case here as the app itself brings up the db, right?

Maybe I'm wrong, and it is not failing because of Heroku not supporting h2, but I don't have any other process listening on port 8082 (at least that I know of and being initiated from within my app)

3 Answers

Answers 1

Heroku will pass port number, you would need to use as enviroment variable with name PORT.

In order to use this variable to set your application port, you have to add line to your application-test.properties :

server.port=${PORT:8082} 

Or application-test.yml:

server:      port: ${PORT:8082} 

In case PORT is not set (like in your local environment), then default 8082 would be used.

This should cover java.net.BindException exception.

Answers 2

The db will not be auto generated unless you add this property in your properties file

spring.jpa.hibernate.ddl-auto = update 

Answers 3

This happens because of a discrepency between how tests are run on eclipse and Heroku. Eclipse runs each test separately, which means it runs each test with fresh start run of the whole application. But Heroku runs all the test classes on one machine sequentially. Therefore I have to kill the h2 server after each test class is finished running:

@AfterClass public static void tearDown() throws SQLException {     webServer.stop(); } 
Read More

Sunday, March 19, 2017

Executing H2 under Spring Boot

Leave a Comment

I've generated a Spring Boot web application using Spring Initializer, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.

Technologies used:

Spring Boot 1.4.2.RELEASE, Spring 4.3.4.RELEASE, Thymeleaf 2.1.5.RELEASE, Tomcat Embed 8.5.6, Maven 3, Java 8

This is a bean I call when starting the DB

@SpringBootApplication @EnableAutoConfiguration @Import({SecurityConfig.class}) public class BookApplication {      public static void main(String[] args) {         SpringApplication.run(BookApplication.class, args);     } }    @Configuration public class PersistenceConfig {  ...      /**          * Creates an in-memory "books" database populated           * with test data for fast testing          */         @Bean         public DataSource dataSource(){             return                 (new EmbeddedDatabaseBuilder())                 .addScript("classpath:db/H2.schema.sql")                 .addScript("classpath:db/H2.data.sql")                 .build();         } 

When I execute this insert in

CREATE TABLE IF NOT EXISTS t_time_lapse (       id          bigint  PRIMARY KEY,       name        varchar(50) NOT NULL,       description varchar(200) NOT NULL,       sunday      boolean DEFAULT NULL,       monday      boolean DEFAULT NULL,       tuesday     boolean DEFAULT NULL,       wednesday   boolean DEFAULT NULL,       thursday    boolean DEFAULT NULL,       friday      boolean DEFAULT NULL,       saturday    boolean DEFAULT NULL,       init_period date    NOT NULL ,       end_period  date    NOT NULL ,       init_time   time    DEFAULT NULL,       end_time    time    DEFAULT NULL,       company_id  bigint DEFAULT NULL,       FOREIGN KEY (company_id)     REFERENCES public.t_company(id)  );    insert into T_TIME_LAPSE (ID, NAME, DESCRIPTION, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY, INIT_PERIOD, END_PERIOD, INIT_TIME, END_TIME, COMPANY_ID)      values (9090,'key', 'key', 1,1,1,1,1,1,1,CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, PARSEDATETIME('03:05:06 GMT','HH:mm:ss z', 'en', 'GMT'), PARSEDATETIME('03:05:06 GMT','HH:mm:ss z', 'en', 'GMT'), 1); 

I got this error

user lacks privilege or object not found: PARSEDATETIME 

Executing the same query in the Data Source Explorer -> DataBase Connections -> SQL Scrapbook everything is fine !

adding SHOW CREATE FUNCTION PARSEDATETIME in the script:

Failed to execute SQL script statement #1 of class path resource [db/H2.data.sql]: SHOW CREATE FUNCTION PARSEDATETIME; nested exception is java.sql.SQLSyntaxErrorException: unexpected token: SHOW 

and CREATE FUNCTION PARSEDATETIME;

Failed to execute SQL script statement #1 of class path resource [db/H2.data.sql]: CREATE FUNCTION PARSEDATETIME; nested exception is java.sql.SQLSyntaxErrorException: unexpected end of statement:  required: ( 

and with the proposed example :

Failed to execute SQL script statement #2 of class path resource [db/H2.data.sql]: INSERT INTO test values (1, CALL PARSEDATETIME('03:05:06 GMT','HH:mm:ss z', 'en', 'GMT')); nested exception is java.sql.SQLSyntaxErrorException: unexpected token: CALL 

3 Answers

Answers 1

For some reason, the installation of the Stored Function PARSEDATETIME die not allow you to access it. Please provide SHOW CREATE FUNCTION PARSEDATETIME. And look through spring's stuff.

Or, more likely, PARSEDATETIME is a Java function, not a MySQL function.

Note: The place where you are using it implies that it is a MySQL function. To use it as a Java function you need to 'bind' it into the INSERT.

Answers 2

Did you try changing your insert statement from

insert into T_TIME_LAPSE (ID, NAME, DESCRIPTION, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY, INIT_PERIOD, END_PERIOD, INIT_TIME, END_TIME, COMPANY_ID)  values (9090,'key', 'key', 1,1,1,1,1,1,1,CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, PARSEDATETIME('03:05:06 GMT','HH:mm:ss z', 'en', 'GMT'), PARSEDATETIME('03:05:06 GMT','HH:mm:ss z', 'en', 'GMT'), 1); 

to

insert into T_TIME_LAPSE (ID, NAME, DESCRIPTION, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY, INIT_PERIOD, END_PERIOD, INIT_TIME, END_TIME, COMPANY_ID)  values (9090,'key', 'key', 1,1,1,1,1,1,1,CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CALL PARSEDATETIME('03:05:06 GMT','HH:mm:ss z', 'en', 'GMT'), CALL PARSEDATETIME('03:05:06 GMT','HH:mm:ss z', 'en', 'GMT'), 1); 

?

Answers 3

I tried to reproduce your issue by creating Spring Boot project from scratch with spring-boot-starter-data-jpa and h2 dependencies. I did two things:

1) Placed your scripts in /resources with schema.sql and data.sql names in order to create and populate database correspondingly. By default Spring Boot will load SQL from those locations as described here.

2) I have configured testdb H2 database in application.properties like this:

# H2 database configuration spring.datasource.url = jdbc:h2:file:~/testdb;DB_CLOSE_ON_EXIT=FALSE  # Enable SQL script scanning in /resources folder spring.jpa.hibernate.ddl-auto=none  # Enable H2 console under http://localhost:8080/console/ for dev purposes spring.h2.console.enabled=true spring.h2.console.path=/console/ 

The result is that H2 database is populated by sample data you provided without any errors (I didn't configure DataSource as you did in PersistenceConfig and nothing more/nothing else).

If you want to stick to custom SQL scripts location, consider configuring your DataSource following this answer http://stackoverflow.com/a/41644743/2402959.

Read More

Tuesday, February 7, 2017

How do I delete duplicates, and update the records that refer to those duplicates in SQL

Leave a Comment

I have two tables:

User:(int id, varchar unique username)  Items: (int id, varchar name, int user_id) 

currently, there are case insensitive duplicates in user table like:

1,John 2,john 3,sally 4,saLlY 

and the Items table will then have

1,myitem,1 2,mynewitem,2 3,my-item,3 4,mynew-item,4 

I've updated the code that inserts to user table to make sure it always inserts lowercase.

However, I need to migrate the database so that duplicates are removed from the user table, and the item table reference is updated so the user doesn't lose access to their items

I.E the data after migration will be:

User:

1,john 3,sally 

Items

1,myitem,1 2,mynewitem,1 3,my-item,3 4,mynew-item,3 

Since the user table has a unique constraint, i can't just set it to lower like

update public.user set username =lower(username) 

7 Answers

Answers 1

Update Items first:

update items set userid = u.userid from items i    inner join users u on i.iserid=u.userid    inner join (select userid, username, row_number() over (partition by username order by userid)) u2 on u2.username=u.username and rn=1 

then create new user table based off original:

select userid, lower(username) username  into NewUserTable from (select userid, username, row_number() over (partition by username order by userid)) u  where rn=1 

Answers 2

The following code is tested with "H2 1.3.176 (2014-04-05) / embedded mode" on the web console. There are two queries that should solve the issue as you stated, and there is an additional preparation statement for considering a case that - though not shown in your data - should be considered, too. The preparation statement will be explained a little bit later; Let's start with the main two queries:

First, all items.userids will be rewritten to those of corresponding user entries with lower case name as follows: Let's call lower case entries main and non lower case entries dup. Then, every items.userid, which refers to a dup.id, will be set to a corresponding main.id. A main entry corresponds to a dup entry if a case-insensitive comparison of their names matches, i.e. main.name = lower(dup.name).

Second, all dup entries in the user table will be deleted. A dup entry is one where name <> lower(name).

So far the basic requirements. Additionally, we should consider that for some users there might exist only entries with upper case characters, but no "lower case entry". For dealing with this situation, a preparation statement is used, which sets - for each group of common names - one name out of each group to lowercase.

drop table if exists usr;  CREATE TABLE usr     (`id` int primary key, `name` varchar(5)) ;  INSERT INTO usr     (`id`, `name`) VALUES     (1, 'John'),     (2, 'john'),     (3, 'sally'),     (4, 'saLlY'),     (5, 'Mary'),     (6, 'mAry')  ;  drop table if exists items;  CREATE TABLE items     (`id` int, `name` varchar(10), `userid` int references usr (`id`)) ;  INSERT INTO items     (`id`, `name`, `userid`) VALUES     (1, 'myitem', 1),     (2, 'mynewitem', 2),     (3, 'my-item', 3),     (4, 'mynew-item', 4) ;  update usr set name = lower(name) where id in (select min(ui.id) as minid from usr ui where lower(ui.name) not in (select ui2.name from usr ui2) group by lower(name));  update items set userid = (select umain.id as mainid from usr udupl, usr umain  where umain.name = lower(umain.name)      and lower(udupl.name) = lower(umain.name)      and udupl.id = userid );  delete from usr where name <> lower(name);  select * from usr;  select * from items; 

Executing above statements yields the following results:

select * from usr; ID  | NAME ----|----- 2   | john 3   | sally 5   | mary  select * from items; ID | NAME     |USERID   ---|----------|------ 1  |myitem    | 2 2  |mynewitem | 2 3  |my-item   | 3 4  |mynew-item| 3 

Answers 3

If you first update correctly the items references, then you can delete the users duplicates. In the following example I kept the users with the minimum id as the correct ones, if this doesn't bother you

--Prepare data create TABLE #users   (id int primary key, username varchar(15));  INSERT INTO #users (id, username) select 1, 'John' union all select 2, 'john' union all select 3, 'sally' union all select 4, 'saLlY' union all select 5, 'Mary' union all select 6, 'mAry'   create TABLE #items   (itemid int, name varchar(10), userid int references #users (id));  INSERT INTO #items (itemid, name, userid) select 1, 'myitem', 1 union all select 2, 'mynewitem', 2 union all select 3, 'my-item', 3 union all select 4, 'mynew-item', 4 ;  --Update items update #items  set userid =minid  from  ( select minid,id from  ( select min(id) as minid,lower(username) as newusername from #users group by username) t inner join #users  on t.newusername = username) t2 inner join #items on t2.id = userid   --delete duplicates users, according to minimum id delete from #users where id not in ( select min(id) from #users group by lower(username))  --set the remaining users names to lower update #users set username = lower(username)  --Clean temp data drop table #users drop table #items  

This was tested in sqlserver, but you asked for pure sql, so I think it will suits you

Answers 4

This code works perfect on SQL Server

Try it it will help you (you may need to simple changes to comply with your DB engine):-

SELECT U1.id,U2.id id2 INTO #User_Tmp FROM User U1 JOIN User U2  ON LOWER(U2.username) = LOWER(U1.username)  AND U1.id < U2.id  UPDATE It SET It.user_id = U.id FROM Items It JOIN #User_Tmp U ON U.id2 = It.id  DELETE FROM User WHERE id IN  (     SELECT id2 FROM #User_Tmp )  SELECT * FROM User  SELECT * FROM Items  DROP TABLE #User_Tmp; 

hope this Answers the question.

Answers 5

BEGIN TRAN CREATE TABLe #User (UserID Int, UserName Nvarchar(255))  INSERT INTO #USER SELECT 1,'John' UNION ALL SELECT 2,'John'  UNION ALL SELECT 3,'sally' UNION ALL SELECT 4,'saLlY'  CREATE TABLE #items   (itemid int, name varchar(10), userid int );  INSERT INTO #items (itemid, name, userid) select 1, 'myitem', 1 union all select 2, 'mynewitem', 2 union all select 3, 'my-item', 3 union all select 4, 'mynew-item', 4  GO WITH CTE (USERID, DuplicateCount) AS (     SELECT UserName,     ROW_NUMBER() OVER(PARTITION BY  UserName     ORDER BY  UserName) AS DuplicateCount     FROM #User  ) Delete from CTE Where DuplicateCount > 1  Select * from #User  Select * from #items  ROLLBACK TRAN 

Answers 6

Try out MERGE statement using this you can find out duplicate and also you can update the values of duplicates.

MERGE [INTO] <target table>

USING <source table or table expression>

ON <join/merge predicate> (semantics similar to outer join)

WHEN MATCHED <statement to run when match found in target>

WHEN [TARGET] NOT MATCHED <statement to run when no match found in target>

Answers 7

I'm not good at H2. You can try this writen for SQL Server and database case sensitive, accent sensitive.

create table t_user(id int not null identity(1,1), username varchar(25) unique); alter table t_user add constraint pk_id_user primary key(id);  create table t_items(id int not null identity(1,1), name varchar(25), user_id int); alter table t_items add constraint pk_id_items primary key(id); alter table t_items add constraint fk_user_id foreign key(user_id) references t_user(id);  insert into t_user (username) values ('John'), ('john'), ('sally'), ('saLlY'); insert into t_items (name, user_id) values ('myitem', 1), ('mynewitem', 2), ('my-item', 3), ('mynew-item',4);  select * from t_user select * from t_items  create table t_user_mig(id int not null identity(1,1), username varchar(25) unique); alter table t_user_mig add constraint pk_id_user_mig primary key(id);  create table t_items_mig(id int not null identity(1,1), name varchar(25), user_id int); alter table t_items_mig add constraint pk_id_items_mig primary key(id); alter table t_items_mig add constraint fk_user_id_mig foreign key(user_id) references t_user_mig(id);  insert into t_user_mig select distinct lower(username) from t_user insert into t_items_mig select ti.name, (select id from t_user_mig where username = lower(tu.username))  from t_items ti, t_user tu  where ti.user_id = tu.id  select * from t_user_mig select * from t_items_mig 

I replace your tables user, items by t_user, t_items. These tables are migrated to t_user_mig, t_items_mig.

You can try it in H2. I'll appreciate your feedback.

I hope it can help.

Read More

Thursday, July 7, 2016

Detecting and recovering failed H2 cluster nodes

Leave a Comment

After going through H2 developer guide I still don't understand how can I find out what cluster node(s) was/were failing and which database needs to be recovered in the event of temporary network failure.

Let's consider the following scenario:

  • H2 cluster started with N active nodes (is actually it true that H2 can support N>2, i.e. more than 2 cluster nodes?)
  • (lots DB updates, reads...)
  • Network connection with one (or several) cluster nodes gets down and node becomes invisible to the rest of the cluster
  • (lots of DB updates, reads...)
  • Network link with previously disconnected node(s) restored
  • It is discovered that cluster node was probably missing (as far as I can see SELECT VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE NAME='CLUSTER' starts responding with empty string if one node in cluster fails)

After this point it is unclear how to find out what nodes were failing? Obviously, I can do some basic check like comparing DB size, but it is unreliable.

  1. What is the recommended procedure to find out what node was missing in the cluster, esp. if query above responds with empty string?

  2. Another question - why urlTarget doesn't support multiple parameters? How I am supposed to use CreateCluster tool if multiple nodes in the cluster failed and I want to recover more than one?

  3. Also I don't understand how CreateCluster works if I had to stop the cluster and I don't want to actually recover any nodes? What's not clear to me is what I need to pass to CreateCluster tool if I don't actually need to copy database.

1 Answers

Answers 1

That is partially right SELECT VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE NAME='CLUSTER', will return an empty string when queried in standard mode.

However, you can get the list of servers by using Connection.getClientInfo() as well, but it is a two-step process. Paraphrased from h2database.com:

The list of properties returned by getClientInfo() includes a numServers property that returns the number of servers that are in the connection list. getClientInfo() also has properties server0..serverN, where N is the number of servers - 1. So to get the 2nd server from the list you use getClientInfo('server1').

Note: The serverX property only returns IP addresses and ports and not hostnames.

And before you say simple replication, yes that is default operation, but you can do more advanced things that are outside the scope of your question in clustered H2.

Here's the quote for what you're talking about:

Clustering can only be used in the server mode (the embedded mode does not support clustering). The cluster can be re-created using the CreateCluster tool without stopping the remaining server. Applications that are still connected are automatically disconnected, however when appending ;AUTO_RECONNECT=TRUE, they will recover from that.

So yes if the cluster stops, auto_reconnect is not enabled, and you stick with the basic query, you are stuck and it is difficult to find information. While most people will tell you to look through the API and or manual, they haven't had to look through this one so, my sympathies.

I find it way more useful to track through the error codes, because you get a real good idea of what you can do when you see how the failure is planned for ... here you go.

Read More

Monday, April 11, 2016

H2 Database Auto Server mode : Accessing through web console remotely

Leave a Comment

I am fairly new to H2 Database. As a part of a PoC, I am using H2 database(version : 1.4.187) for mocking the MS SQL Server DB. I have one application, say app1 which generates the data and save into H2. Another application, app2, needs to read from the H2 database and process the data it reads. I am trying to use Auto Server mode so that even if one of the application is down, other one is able to read/write to/from the database.

After reading multiple examples, i found how to build the h2 url and shown as below:

jdbc:h2:~/datafactory;MODE=MSSQLServer;AUTO_SERVER=TRUE; 

Enabled the tcp and remote access as Below:

org.h2.tools.Server.createTcpServer("-tcpAllowOthers","-webAllowOthers").start() 

With this, I am able to write to the database. Now, I want to read the data using the h2-web-console application. I am able to do that from my local machine. However, I am not able to understand how I can connect to this database remotely from another machine.

My plant is to run these two apps in an ubuntu machine and I can monitor the data using the web console from my machine. Is it not possible with this approach? How can I solve this ?

Or do I need to use server mode and explicitly start the h2 server? Any help would be appreciated.

2 Answers

Answers 1

By default, remote connections are disabled for H2 database for protection. To enable remote access to the TCP server, you need to start the TCP server using the option -tcpAllowOthers or the other flags -webAllowOthers, -pgAllowOthers .

To start both the Web Console server (the H2 Console tool) and the TCP server with remote connections enabled, you will have to use something like below

java -jar /path/to/h2.jar -web -webAllowOthers -tcp -tcpAllowOthers -browser 

More information can be found in the docs here and console settings can be configured from here

Answers 2

Not entirely sure but looking at the documentation and other questions answered previously regarding the same topic the url should be something like this:

jdbc:h2:tcp://<host>:<port>/~/datafactory;MODE=MSSQLServer;AUTO_SERVER=TRUE; 

It seems that the host may not be localhost and the database may not be in memory

Read More