database

Using Ora2Pg to Migrate Data from Oracle Database to PostgreSQL

Ora2Pg is a free tool to migrate Oracle (and MySQL) databases to PostgreSQL. It connects to an Oracle database, scans it automatically, extracts its structure or data, and generates SQL scripts which we can load directly into PostgreSQL.

For this blog post, I’m trying to do a simple data migration from Oracle to PostgreSQL, both of which are running as containers.

Oracle

Let’s start by running our Oracle instance.

$ podman run  -p 1521:1521 \
        -e ORACLE_PASSWORD='YourStrongPass123' \
        -e APP_USER=db_test \
        -e APP_USER_PASSWORD='DbTest123' \
        -v oradata:/opt/oracle/oradata \
        docker.io/gvenzl/oracle-free:slim
		
Trying to pull docker.io/gvenzl/oracle-free:slim...
Getting image source signatures
Copying blob 0478c6754a70 done   |
Copying blob 4a89c51c0a93 done   |
Copying config 41bcf6415d done   |
Writing manifest to image destination
CONTAINER: starting up...
CONTAINER: first database startup, initializing...
CONTAINER: uncompressing database data files, please wait...
CONTAINER: done uncompressing database data files, duration: 2 seconds.
CONTAINER: starting up Oracle Database...

Connect with the user db_test and password DbTest123, then run the queries below

CREATE TABLE tb_football_clubs (
    club_id       NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name          VARCHAR2(100) NOT NULL UNIQUE,
    city          VARCHAR2(50)  NOT NULL,
    country       VARCHAR2(50)  NOT NULL,
    stadium       VARCHAR2(100),
    founded_year  NUMBER(4)
);

CREATE TABLE tb_football_players (
    player_id     NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    club_id       NUMBER NOT NULL,
    full_name     VARCHAR2(100) NOT NULL,
    position      VARCHAR2(20) NOT NULL,
    nationality   VARCHAR2(50),
    shirt_number  NUMBER(2),
    CONSTRAINT fk_player_club FOREIGN KEY (club_id)
        REFERENCES tb_football_clubs (club_id),
    CONSTRAINT uq_club_shirt UNIQUE (club_id, shirt_number)
);

CREATE INDEX idx_players_club ON tb_football_players (club_id);

INSERT INTO tb_football_clubs (name, city, country, stadium, founded_year) VALUES ('Real Madrid', 'Madrid', 'Spain', 'Santiago Bernabeu', 1902);
INSERT INTO tb_football_clubs (name, city, country, stadium, founded_year) VALUES ('FC Barcelona', 'Barcelona', 'Spain', 'Camp Nou', 1899);
INSERT INTO tb_football_clubs (name, city, country, stadium, founded_year) VALUES ('Manchester City', 'Manchester', 'England', 'Etihad Stadium', 1880);
INSERT INTO tb_football_clubs (name, city, country, stadium, founded_year) VALUES ('Liverpool', 'Liverpool', 'England', 'Anfield', 1892);
INSERT INTO tb_football_clubs (name, city, country, stadium, founded_year) VALUES ('Bayern Munich', 'Munich', 'Germany', 'Allianz Arena', 1900);

INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (1, 'Jude Bellingham', 'Midfielder', 'England', 5);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (1, 'Kylian Mbappe', 'Forward', 'France', 10);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (2, 'Lamine Yamal', 'Forward', 'Spain', 10);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (3, 'Erling Haaland', 'Forward', 'Norway', 9);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (3, 'Phil Foden', 'Midfielder', 'England', 47);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (4, 'Virgil van Dijk', 'Defender', 'Netherlands', 4);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (4, 'Alisson Becker', 'Goalkeeper', 'Brazil', 1);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (4, 'Florian Wirtz', 'Midfielder', 'Germany', 7);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (4, 'Dominik Szoboszlai', 'Midfielder', 'Hungary', 8);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (4, 'Hugo Ekitike', 'Forward', 'France', 22);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (5, 'Manuel Neuer', 'Goalkeeper', 'Germany', 1);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (5, 'Jamal Musiala', 'Midfielder', 'Germany', 10);
INSERT INTO TB_FOOTBALL_PLAYERS (club_id, full_name, position, nationality, SHIRT_NUMBER) VALUES (5, 'Harry Kane', 'Forward', 'England', 1);

Run the following query to verify the inserted data,

SELECT (SELECT banner FROM v$version), c.name AS club, p.full_name, p.position, p.shirt_number
FROM tb_football_players p
JOIN tb_football_clubs c ON c.club_id = p.club_id
ORDER BY c.name, p.shirt_number;

PostgreSQL

Now let’s start and initialize our PostgreSQL instance,

$ podman run  -p 5432:5432 \
        -e POSTGRES_USER=postgres \
        -e POSTGRES_PASSWORD='password' \
        -e POSTGRES_DB=db_test \
        docker.io/library/postgres:17

Trying to pull docker.io/library/postgres:17...
Getting image source signatures
Copying blob 3be737420556 done   |
Copying blob ecc510c1e359 done   |
Copying config 327daa8fae done   |
Writing manifest to image destination
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /var/lib/postgresql/data ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default "max_connections" ... 100
selecting default "shared_buffers" ... 128MB
selecting default time zone ... Etc/UTC
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

Create PostgreSQL table,

CREATE TABLE tb_football_clubs (
    club_id       BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name          VARCHAR(100) NOT NULL UNIQUE,
    city          VARCHAR(50)  NOT NULL,
    country       VARCHAR(50)  NOT NULL,
    stadium       VARCHAR(100),
    founded_year  NUMERIC(4)
);

CREATE TABLE tb_football_players (
    player_id     BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    club_id       BIGINT NOT NULL,
    full_name     VARCHAR(100) NOT NULL,
    position      VARCHAR(20) NOT NULL,
    nationality   VARCHAR(50),
    shirt_number  NUMERIC(2),
    CONSTRAINT fk_player_club FOREIGN KEY (club_id)
        REFERENCES tb_football_clubs (club_id),
    CONSTRAINT uq_club_shirt UNIQUE (club_id, shirt_number)
);

CREATE INDEX idx_players_club ON tb_football_players (club_id);

Ora2Pg

We can start downloading Ora2Pg by running this command,

$ podman pull georgmoser/ora2pg

Create directories to store ora2pg configurations

$ mkdir -p ora2pg/config/

Next, create an ora2pg.conf file under the ora2pg/config/ folder with the following configuration

ORACLE_DSN   dbi:Oracle:host=host.containers.internal;port=1521;service_name=FREEPDB1
ORACLE_USER  db_test
ORACLE_PWD   DbTest123
SCHEMA       DB_TEST


PG_DSN       dbi:Pg:dbname=db_test;host=host.containers.internal;port=5432
PG_USER      postgres
PG_PWD       password

TYPE         COPY
DATA_LIMIT   10000
NLS_LANG     AMERICAN_AMERICA.UTF8
USER_GRANTS  1
NO_START_SCN   1
PG_VERSION     17

Since we are using Podman, we set the database host to host.containers.internal

Run the Ora2Pg from our image using Podman,

$ podman run --rm \
                    -v ~/Documents/ora2pg/config:/config:Z \
                    georgmoser/ora2pg \
                    ora2pg -t COPY -c /config/ora2pg.conf --no_start_scn

INFO: No CONFIG_LOCATION variable provided. Using default '/config/ora2pg.conf'
INFO: No OUTPUT_LOCATION variable provided. Using default '/data'
INFO: No ORA_HOST variable provided. Using value of 'ORACLE_DSN' from '/config/ora2pg.conf'
INFO: No ORA_USER variable provided. Using value of 'ORACLE_USER' from  '/config/ora2pg.conf'
INFO: No ORA_PWD variable provided. Using value of 'ORACLE_PWD' from  '/config/ora2pg.conf'
INFO: executing: 'ora2pg -t COPY -c /config/ora2pg.conf --no_start_scn'
[2026-10-08 06:14:14] [============>            ] 1/2 tables (50.0%) scanning table TB_FOOTBALL_CLUB[2026-10-08 06:14:14] [========================>] 2/2 tables (100.0%) scanning table TB_FOOTBALL_PLA[2026-10-08 06:14:14] [========================>] 2/2 tables (100.0%) end of scanning.              
[2026-10-08 06:14:24] [========================>] 5/5 rows (100.0%) Table TB_FOOTBALL_CLUBS (5 recs/sec)
[2026-10-08 06:14:24] [=======>                 ]  5/17 total rows (29.4%) - (0 sec., avg: 5 recs/sec).
[2026-10-08 06:14:24] [=======>                 ]  5/17 rows (29.4%) on total estimated data (1 sec.[2026-10-08 06:14:24] [========================>] 12/12 rows (100.0%) Table TB_FOOTBALL_PLAYERS (12 recs/sec)
[2026-10-08 06:14:24] [========================>] 17/17 total rows (100.0%) - (0 sec., avg: 17 recs/sec).

[2026-10-08 06:14:24] [========================>] 17/17 rows (100.0%) on total estimated data (1 secNOTICE:  relation "iseq$$_73355" does not exist, skipping
NOTICE:  relation "iseq$$_73359" does not exist, skipping
NOTICE:  relation "iseq$$_73369" does not exist, skipping
NOTICE:  relation "iseq$$_73373" does not exist, skipping
Fixing function calls in output files...

Schema Export Complete

And we can see the result of our migrated data using the below query,

SELECT version(), c.name AS club, p.full_name, p.position, p.shirt_number
FROM tb_football_players p
JOIN tb_football_clubs c ON c.club_id = p.club_id
ORDER BY c.name, p.shirt_number;

With the below result,

Fixing MySql’s Error, “Cant create test file /folder/data/servername.lower-test” on RedHat OS

Had a very weird error starting my MySql after im moving MySql’s data folder from /var/lib/mysql to /folder/data on my RHEL 7 Server,

2017-03-23 11:50:52 2119 [Warning] Can't create test file /folder/data/servername.lower-test
2017-03-23 11:50:52 2119 [Warning] Can't create test file /folder/data/servername.lower-test
2017-03-23 11:50:52 2119 [ERROR] /usr/sbin/mysqld: Can't create/write to file 
    '/folder/data/servername.lower-test' (Errcode: 13 - Permission denied)

very very weird because im already change the ownership of my /folder data to mysql user, and still not working. I even chmod it to 777 for testing purpose, yet still no positive result. After a while i found out that my SELinux is blocking it, here is the command to unblock it.

[root@servername ~]# setenforce 0
[root@servername ~]# getenforce
Permissive

but the commands above only configure your SELinux until reboot. If you want to make it permanent, you could use this command,

[root@servername ~]# vi /etc/selinux/config 
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=permissive
# SELINUXTYPE= can take one of these two values:
#     targeted - Targeted processes are protected,
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted  

Hope it helps others, cheers

[Java] org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (ORA-00923: FROM keyword not found where expected

Had this weird error, when connecting to Oracle Database

### The error occurred while executing a query
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; 
nested exception is org.apache.commons.dbcp.SQLNestedException: 
Cannot create PoolableConnectionFactory (ORA-00923: FROM keyword not found where expected
)

Somehow it never happens on my MySql Configuration. Here is my configuration,

<bean id="dataSourceOracle" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
          p:driverClassName="oracle.jdbc.driver.OracleDriver" p:url="${db.url.oracle}"
          p:username="${db.username.oracle}" p:password="${db.password.oracle}"
          p:initialSize="2"
          p:maxActive="30"
          p:maxIdle="10"
          p:minIdle="3"
          p:maxWait="30000"
          p:removeAbandoned="true"
          p:removeAbandonedTimeout="30"
          p:validationQuery="SELECT 1" />

After investigating for a while, i found out that it happens because i use MySql’s specific validationQuery, “SELECT 1”. Changing it into “SELECT 1 FROM DUAL” make the error go away. 😛

[Java] Cara Cache Data Hasil Query dengan SpringMVC

Pada tulisan kali ini, saya coba membuat suatu metode caching untuk menyimpan data-data master kedalam memory, sehingga tidak perlu query ke database lagi. Hal ini cocok digunakan untuk menyimpan data-data master yang sangat jarang berubah, seperti kode provinsi ataupun kode cabang.

Okay, di tutorial ini saya menggunakan Spring MVC 4, Hibernate dan ehCache sebagai cache provider. Berikut adalah pom.xml yang digunakan,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>mavenproject5</artifactId>
    <version>1</version>
    <packaging>war</packaging>

    <name>mavenproject5</name>

    <properties>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        
        
        <!-- Spring 4 dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>4.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>4.1.2.RELEASE</version>
         </dependency>
         
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        
        
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.2.2</version>
        </dependency>
        
        <!--ehcache-->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>
        
        <!--hibernate-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.3.8.Final</version>
        </dependency>
        
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>7.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Kemudian konfigurasi standard yang digunakan di SpringMVC, applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

    <context:annotation-config/>
    
    <tx:annotation-driven/>
    
    <cache:annotation-driven />
    
    <context:component-scan base-package="com.edw.mavenproject5.service"/>
    
     <!-- datasource  -->
    <bean id="ds" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close"
          p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://localhost/test"
          p:username="root" p:password=""
          p:initialSize="10"
          p:maxActive="50"
          p:maxIdle="10"
          p:minIdle="3"
          p:maxWait="30000"
          p:removeAbandoned="true"
          p:removeAbandonedTimeout="30"
          p:validationQuery="SELECT 1" />
    
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
        p:dataSource-ref="ds" p:configLocations="classpath:hibernate.cfg.xml" depends-on="ds">
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=org.hibernate.dialect.MySQLDialect
                hibernate.query.substitutions=true
                hibernate.show_sql=true
                hibernate.enable_lazy_load_no_trans=true
            </value>
        </property>
    </bean> 
    
    <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <bean id="cacheManager" 
          class="org.springframework.cache.ehcache.EhCacheCacheManager" 
          p:cache-manager-ref="ehcache"/>
    <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
          p:config-location="classpath:ehcache.xml"/>
    
</beans>

dan dispatcher-servlet.xml,

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">


    <context:component-scan base-package="com.edw.mavenproject5.controller" />
	
    <mvc:annotation-driven />
    <mvc:default-servlet-handler />

    <bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/jsp/"
          p:suffix=".jsp" />
</beans>

yang kemudian dipanggil dari web.xml,

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

Nah, ada satu konfigurasi xml yang penting dalam membuat cache. Yaitu ehcache.xml, disana disimpan nama cache serta lama waktu hidupnya

<?xml version="1.0" encoding="UTF-8"?>
<!--
    caching configuration
-->
<ehcache>
    
    <defaultCache
        maxElementsInMemory="5000"
        eternal="true"
        overflowToDisk="false"
        memoryStoreEvictionPolicy="LRU"
    />
    
    <!--2menit-->
    <cache name="duaMenit"
           maxElementsInMemory="10000"
           timeToLiveSeconds="120"
           memoryStoreEvictionPolicy="LRU" 
    />
    
    <!--1menit-->
    <cache name="satuMenit"
           maxElementsInMemory="10000"
           timeToLiveSeconds="60"
           memoryStoreEvictionPolicy="LRU" 
    />
</ehcache>

Nah sekarang kita buat class java-nya, dimulai dari service layer. Perhatikan annotation Cacheable, konfigurasi tersebut adalah konfigurasi yang menandakan lokasi method yang kita cache.

package com.edw.mavenproject5.service;

import com.edw.mavenproject5.bean.Dosen;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class DosenService {

    @Autowired
    private SessionFactory sessionFactory;
    
    @Cacheable(key = "#root.methodName", value = "duaMenit")
    public List<Dosen> getDosens() {
        return sessionFactory.getCurrentSession().createCriteria(Dosen.class).list();
    }
    
    @Cacheable(key = "#root.methodName + #idDosen", value = "satuMenit")
    public Dosen getDosen(String idDosen) {
        return (Dosen) sessionFactory.getCurrentSession().get(Dosen.class, idDosen);
    }
    
}

Yang kemudian dipanggil dari controller,

package com.edw.mavenproject5.controller;

import com.edw.mavenproject5.service.DosenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class IndexController {
    
    @Autowired
    private DosenService dosenService;
    
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String index(ModelMap modelMap){
        modelMap.put("dosens", dosenService.getDosens());
        return "index";
    }
    
    @RequestMapping(value = "/get", method = RequestMethod.GET)
    public String get(ModelMap modelMap, String id){
        modelMap.put("dosen", dosenService.getDosen(id));
        return "index";
    }
}

Dan berikut adalah ui yang digunakan untuk menampilkan hasil query didatabase,

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <h1>Hello World!</h1>
        
        <c:if test="${not empty dosens}">
        <table class="table table-hover table-striped">
            <thead>
                <tr>
                    <th>ID Dosen</th>
                    <th>Nama Dosen</th>
                </tr>
            </thead>
            <tbody>
            <c:forEach items="${dosens}" var="dosen">
                <tr>
                    <td>${dosen.iddosen}</td>
                    <td>${dosen.namadosen}</td>
                </tr>
            </c:forEach>
            </tbody>
        </table>
        </c:if>
        
        <c:if test="${not empty dosen}">
            <h1>Hello Dosen ${dosen.namadosen}!</h1>
        </c:if>
    </body>
</html>

Hasil akhirnya adalah sebagai berikut, data dengan kode 02 dan 321 sudah diubah secara langsung didatabase, namun walaupun browser di-refresh berulang kali, tetap menampilkan data yang lama, yaitu “Dodol” dan “Testing 123 123”.
capture1

Untuk sourcecode lengkapnya bisa diunduh di github, https://github.com/edwinkun/CachingWithSpringMVC. (*)