Run a Micronaut application on Tomcat
Tags: micronaut, java, doma
Published: 2024-01-28 18:03:01

## Intro
I will try running my Micronaut application on Tomcat in this time.
At first, I tried to access SQL Server using Micronaut R2DBC, as in previous blog posts.

* [[Micronaut] Accessing SQL Server 1](https://dev.to/masanori_msl/mictonaut-accessing-sql-server-1-1b3a)
* [[Micronaut] Accessing SQL Server 2](https://dev.to/masanori_msl/mictonaut-accessing-sql-server-2-o92)
* [[Micronaut] Accessing SQL Server 3](https://dev.to/masanori_msl/micronaut-accessing-sql-server-3-1432)

But I couldn't figure out how to use the DataSource what is gotten from JNDI, so I decided to use Doma2.

* [domaframework/doma - GitHub](https://github.com/domaframework/doma)
* [Welcome to Doma 2 - Doma documentation](https://doma.readthedocs.io/en/latest/)

I install Tomcat ver.10.1.18 on Fedora 39 first.

* [Apache Tomcat 10(10.1.18) - Documentation Index](https://tomcat.apache.org/tomcat-10.1-doc/index.html)
* [Fedora 36にTomcat9.0.63をインストールする方法 - 最新IT技術情報_arkgame.com](https://arkgame.com/2022/05/21/post-308203/)

## Generating a war file and running on Tomcat
To generate a war file and run on Tomcat, I change "build.gradle".

#### build.gradle.kts
```java
plugins {
    id("com.github.johnrengelman.shadow") version "8.1.1"
    id("io.micronaut.application") version "4.2.1"
    id("io.micronaut.aot") version "4.2.1"

    // add this
    id("war")
}
...
graalvmNative.toolchainDetection.set(false)
micronaut {

    // change from "netty"
    runtime("tomcat")
    
    testRuntime("junit5")
    processing {
        incremental(true)
        annotations("jp.masanori.*")
    }
    aot {
        optimizeServiceLoading.set(false)
        convertYamlToJava.set(false)
        precomputeOperations.set(true)
        cacheEnvironment.set(true)
        optimizeClassLoading.set(true)
        deduceEnvironment.set(true)
        optimizeNetty.set(true)
    }
}
```

After that, I can generate a war file by "./gradlew war".
The generated war file is put into "{ProjectFolder}/build/libs".

To install into Tomcat, I move the war file into "/opt/tomcat/webapps".

Now, I can access the application on "http://localhost:8080/domasample".
(The file what I put is named "domasample.war")

## Adding and getting JNDI
To get connection strings from JNDI, I add some strings into "context.xml" and "server.xml" of Tomcat.

#### context.xml
```xml
...
<Context>
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
    <ResourceLink name="jdbc/mssql"
                global="jdbc/mssql"
                auth="Container"
                type="javax.sql.DataSource" />
</Context>
```

#### server.xml
```xml
...
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
    <Resource name="jdbc/mssql" auth="Container"
            type="javax.sql.DataSource"
        driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
        url="jdbc:sqlserver://localhost:1433;encrypt=false;DatabaseName=master"
        username="sa"
        password="example" />
  </GlobalNamingResources>
...
```

* [Tomcat DataSource JNDI Example in Java - DigitalOcean](https://www.digitalocean.com/community/tutorials/tomcat-datasource-jndi-example-java)

And I can get JNDI data as DataSource like below.

```java
import javax.sql.DataSource;
import javax.naming.InitialContext;
import javax.naming.NamingException;
...
    try {
      DataSource dataSource = (DataSource)(new InitialContext()).lookup("java:/comp/env/jdbc/mssql");
      
    }catch(NamingException e) {
    }
...
```

* [DataSource - Java Platform SE 8](https://docs.oracle.com/javase/8/docs/api/javax/sql/DataSource.html)
* [Connection - Java Platform SE 8](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html)

The problem was I couldn't figure out how to use the DataSource into R2DBC.
"application.yml(application.properties)" has "datasources.*.jndi-name".
But after I set the JNDI name, I couldn't connect.

* [Configuration Reference - Micronaut](https://micronaut-projects.github.io/micronaut-sql/latest/guide/configurationreference.html)

## Using Doma2
In this time, I have only confirmed that Doma2 can connect to SQL Server.
I'll look into it a little more closely next time.

#### build.gradle.kts
```java
plugins {
    id("com.github.johnrengelman.shadow") version "8.1.1"
    id("io.micronaut.application") version "4.2.1"
    id("io.micronaut.aot") version "4.2.1"
    id("war")
}

version = "0.1"
group = "jp.masanori"

repositories {
    mavenCentral()
}
dependencies {
    annotationProcessor("org.projectlombok:lombok")
    annotationProcessor("io.micronaut:micronaut-http-validation")
    annotationProcessor("io.micronaut.serde:micronaut-serde-processor")
    annotationProcessor("org.seasar.doma:doma-processor:2.55.2")
    implementation("io.micronaut.serde:micronaut-serde-jackson")
    implementation("org.seasar.doma:doma-core:2.55.2")
    compileOnly("io.micronaut:micronaut-http-client")
    compileOnly("org.projectlombok:lombok")
    runtimeOnly("ch.qos.logback:logback-classic")
    runtimeOnly("com.microsoft.sqlserver:mssql-jdbc")
    testImplementation("io.micronaut:micronaut-http-client")
}
application {
    mainClass.set("jp.masanori.Application")
}
java {
    sourceCompatibility = JavaVersion.toVersion("17")
    targetCompatibility = JavaVersion.toVersion("17")
}
graalvmNative.toolchainDetection.set(false)
micronaut {
    runtime("tomcat")
    testRuntime("junit5")
    processing {
        incremental(true)
        annotations("jp.masanori.*")
    }
    aot {
        optimizeServiceLoading.set(false)
        convertYamlToJava.set(false)
        precomputeOperations.set(true)
        cacheEnvironment.set(true)
        optimizeClassLoading.set(true)
        deduceEnvironment.set(true)
        optimizeNetty.set(true)
    }
}
```

#### User.java
```java
package jp.masanori.users.models;

import java.time.LocalDateTime;

import org.seasar.doma.Column;
import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import org.seasar.doma.Table;
import io.micronaut.serde.annotation.Serdeable;
import lombok.Getter;
import lombok.Setter;

@Serdeable
@Entity
@Table(name = "users")
public class User {
    @Id
    @Getter
    @Setter
    public Long id;
    @Getter
    @Setter
    public String name;
    @Getter
    @Setter
    @Column(name = "last_update_date")
    public LocalDateTime lastUpdateDate;
}
```
#### UserDao.java
```java
package jp.masanori.users.dao;

import org.seasar.doma.Dao;
import org.seasar.doma.Select;
import org.seasar.doma.Sql;
import jp.masanori.DaoConfig;
import jp.masanori.users.models.User;

@Dao
@DaoConfig
public interface UserDao {
    @Select
    @Sql("SELECT * FROM users WHERE id = /*userId*/0")
    User findById(Long userId);
}
```

#### DaoConfig.java
```java
package jp.masanori;

import org.seasar.doma.AnnotateWith;
import org.seasar.doma.Annotation;
import org.seasar.doma.AnnotationTarget;

import jakarta.inject.Singleton;

@AnnotateWith(annotations = @Annotation(target = AnnotationTarget.CLASS, type = Singleton.class))
public @interface DaoConfig {   
}
```

#### DomaConfig.java
```java
package jp.masanori;

import javax.sql.DataSource;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.seasar.doma.jdbc.dialect.MssqlDialect;
import org.seasar.doma.jdbc.tx.LocalTransactionDataSource;
import org.seasar.doma.jdbc.tx.LocalTransactionManager;
import org.seasar.doma.jdbc.tx.TransactionManager;

import jakarta.inject.Singleton;

@Singleton
public class DomaConfig implements Config {

  private final LocalTransactionDataSource dataSource;

  private final LocalTransactionManager transactionManager;

  public DomaConfig() {
    LocalTransactionDataSource dataSource;
    try {
      dataSource = new LocalTransactionDataSource((DataSource)(new InitialContext()).lookup("java:/comp/env/jdbc/mssql"));
      
    }catch(NamingException e) {
      dataSource = new LocalTransactionDataSource("jdbc:sqlserver://localhost:1433;encrypt=false;databaseName=master;", "sa", "example");
    }
    this.dataSource = dataSource;
    this.transactionManager = new LocalTransactionManager(this.dataSource.getLocalTransaction(getJdbcLogger()));
  }
  @Override
  public DataSource getDataSource() {
    return dataSource;
  }
  @Override
  public Dialect getDialect() {
    return new MssqlDialect();
  }
  @Override
  public TransactionManager getTransactionManager() {
    return transactionManager;
  }
}
```

#### UserController.java
```java
package jp.masanori.users;

import org.seasar.doma.jdbc.tx.TransactionManager;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import jp.masanori.DomaConfig;
import jp.masanori.users.dao.UserDao;
import jp.masanori.users.models.User;

@Controller("/")
public class UserController {
    private final UserDao users;
    private final DomaConfig domaConf;
    public UserController(UserDao users, DomaConfig domaConf) {
        this.users = users;
        this.domaConf = domaConf;
    }
    @Get("/users")
    public User findUser() {
        TransactionManager tm = domaConf.getTransactionManager();
        return tm.required(() -> {
            return users.findById(1L);
        });       
    }
}
```
* [Transaction - Doma documentation](https://doma.readthedocs.io/en/2.55.2/transaction/)
* [Micronaut + Doma2 連携(超簡易版) - nemuzukaの「明日から本気出す」](https://nemuzuka.vss.jp.net/entry/2020/07/05/164921)