Last Modified 2.3.2023

Test Environment
Tomcat 9
Java 17
Workspace C:\www

C:\ Command Prompt
C:\www>mvn archetype:generate -Dfilter=maven-archetype-webapp

Choose archetype:
1: remote -> com.haoxuer.maven.archetype:maven-archetype-webapp
2: remote -> com.lodsve:lodsve-maven-archetype-webapp
3: remote -> org.apache.maven.archetypes:maven-archetype-webapp
4: remote -> org.bytesizebook.com.guide.boot:maven-archetype-webapp
Choose a number or apply filter: : 3
Choose org.apache.maven.archetypes:maven-archetype-webapp version:
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
6: 1.3
7: 1.4
Choose a number: 7: ↵
Define value for property 'groupId': net.java_school
Define value for property 'artifactId': mybatisspring
Define value for property 'version' 1.0-SNAPSHOT: : ↵
Define value for property 'package' net.java_school: : ↵
Confirm properties configuration:
groupId: net.java_school
artifactId: mybatisspring
version: 1.0-SNAPSHOT
package: net.java_school
 Y: : ↵

Maven creates the mybatisspring folder in C:\www through the build process. C:\www\mybatisspring is the root directory of your project. The src/main/webapp folder, just above WEB-INF, is the document base.

Spring MVC Test

Modify pom.xml like below.

pom.xml
<?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>net.java_school</groupId>
  <artifactId>mybatisspring</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>mybatisspring Maven Webapp</name>
  <url>http://localhhost:8080</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <spring.version>5.3.33</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!-- Servlet JSP -->
    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>javax.servlet.jsp-api</artifactId>
      <version>2.3.3</version>
      <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>mybatisspring</finalName>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
          <configuration>
            <filesets>
               <fileset>
                  <directory>src/main/webapp/WEB-INF/classes</directory>
               </fileset>
               <fileset>
                  <directory>src/main/webapp/WEB-INF/lib</directory>
               </fileset>
            </filesets>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

src/main/webapp/WEB-INF/web.xml

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0">

  <display-name>MyBatis Spring</display-name>

  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <servlet>
    <servlet-name>mybatisspring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/app-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>mybatisspring</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

src/main/webapp/WEB-INF/app-config.xml

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

<mvc:resources mapping="/resources/**" location="/resources/" />

<mvc:annotation-driven />

<bean id="internalResourceViewResolver"
      class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
  <property name="prefix" value="/WEB-INF/views/" />
  <property name="suffix" value=".jsp" />
</bean>

</beans>

Homepage

src/main/webapp/resources/css/styles.css

styles.css
html, body {
  margin: 0;
  padding: 0;
  background-color: #FFF;
  font-family: "Liberation Sans", Helvetica, sans-serif;
}
#wordcard {
  margin: 7px auto;
  padding: 1em;
  border: 3px solid grey;
  width: 600px;
  text-align: center;
}

src/main/webapp/WEB-INF/views/index.jsp

index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Spring MVC Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="Keywords" content="Spring MVC Test" />
<meta name="Description" content="This is web app for Spring MVC Test" />
<link href="./resources/css/styles.css" rel="stylesheet" />
</head>
<body>

<div id="wordcard">
<h1>Vocabulary</h1>
<p>
total number of words which (with rules for combining them) make up a language
</p>
</div>

</body>
</html>

The screen shows words and their meanings.

When the design is complete, it is common to start with database design, then implement JavaBeans, DAO, service, and controller in that order.
To experience Spring MVC more quickly, let's omit most of these and create an example that works only with controllers and views.

Controller
src/main/java/net/java_school/english/HomeController.java

package net.java_school.english;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

  @GetMapping("/")
  public String homepage() {
    return "index";
  }
}

For the homepage ("/") to be served through the controller, you should not declare any other servlets mapped to the homepage. However, the archetype has the index.jsp in the src/main/webapp folder. This file will respond to homepage requests. Delete the JSP file created by Maven when creating the archetype.

Register the controller

<bean id="homeController" class="net.java_school.english.HomeController" />

ROOT Application
Create a ROOT.xml file in the CATALINA_HOME/conf/Catalina/localhost folder.
CATALINA_HOME: Represents the root of your Tomcat installation, for example C:\apache-tomcat-9.0.87.

ROOT.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context
  docBase="C:/www/mybatisspring/src/main/webapp"
  reloadable="true">
</Context>

Build and deploy
Execute the following in the maven root directory:
mvn compile war:inplace
The war:inplace option creates bytecode in src/main/WEB-INF/classes and dependent libraries in src/main/WEB-INF/lib.

Test
Start Tomcat.
Visit http://localhost:8080
Spring MVC

We figured out how Spring MVC works.
Next, practice how the controller delivers data to a view.
We won't use a database to keep the example simple.

Java Beans
src/main/java/net/java_school/english/WordCard.java

WordCard.java
package net.java_school.english;

public class WordCard {
  private String word;
  private String definitions;

  public WordCard() {}

  public WordCard(String word, String definitions) {
    this.word = word;
    this.definitions = definitions;
  }

  public String getWord() {
    return word;
  }

  public void setWord(String word) {
    this.word = word;
  }

  public String getDefinitions() {
    return definitions;
  }

  public void setDefinitions(String definitions) {
    this.definitions = definitions;
  }
}

DAO
src/main/java/net/java_school/english/WordCardDao.java

WordCardDao.java
package net.java_school.english;

public class WordCardDao {

  private final String[][] words = {
    {"illegible",
    	"difficult or impossible to read"}, 
    {"vulnerable",
    	"that is liable to be damaged; not protected against attack"}, 
    {"abate",
    	"(of winds, storms, floods, pain, etc) make or become less"}, 
    {"clandestine",
    	"secret; done secretly; kept secret"}, 
    {"sojourn",
    	"(make a) stay (with sb, at or in) for a time"}, 
    {"defiance",
    	"open disobedience or resistance; refusal to recognize authority; defyling"}, 
    {"infraction",
    	"breaking of a rule, law, etc; instance of this"}
  }; 
	
  public WordCard selectOne() {
    int no = (int)(Math.random() * 7) + 1;
    WordCard wordCard = new WordCard();
    wordCard.setWord(words[no - 1][0]);
    wordCard.setDefinitions(words[no - 1][1]);
    return wordCard;
  }
}
<bean id="wordCardDao" class="net.java_school.english.WordCardDao" />

Service
src/main/java/net/java_school/english/WordCardService.java

WordCardService.java
package net.java_school.english;

public class WordCardService {
  private WordCardDao wordCardDao;

  public WordCardService(WordCardDao wordCardDao) {
    this.wordCardDao = wordCardDao;
  }

  public WordCard getWordCard() {
    return wordCardDao.selectOne();
  }
}
<bean id="wordCardService" class="net.java_school.english.WordCardService">
  <constructor-arg ref="wordCardDao" />
</bean>

Controller

package net.java_school.english;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.GetMapping;

public class HomeController {

  private WordCardService wordCardService;

  public HomeController(WordCardService wordCardService) {
    this.wordCardService = wordCardService;
  }

  @GetMapping("/")
  public String homepage(Model model) {
    WordCard wordCard = wordCardService.getWordCard();
    model.addAttribute("wordCard", wordCard);
    return "index";
  }
}
<bean id="homeController" class="net.java_school.english.HomeController">
  <constructor-arg ref="wordCardService" />
</bean>

View
src/main/webapp/WEB-INF/views/index.jsp

Modify the content of the body element as below.

<div id="wordcard">
<h1>${wordCard.word }</h1>
<p>
${wordCard.definitions }
</p>
</div>

Test
mvn clean compile war:inplace
http://localhost:8080
Test 2

Using Database

Let's modify our app to use MyBatis-Spring.
MyBatis-Spring is an interlocking module for conveniently using MyBatis in Spring.
MyBatis is a SQL mapping persistence framework.
See https://mybatis.org/spring/getting-started.html

Modify Homepage
Add the following form after #wordcard in the index.jsp.

src/main/webapp/WEB-INF/views/index.jsp

<form id="new-form" method="post">
  <input type="text" name="word" />
  <input type="text" name="definitions" />
  <input type="submit" value="Add" style="color: grey;" />
</form>

Add the following styles to your stylesheet.

src/main/webapp/resources/css/styles.css

#new-form {
  margin: 7px auto;
  padding-left: 2em;
  width: 600px;
  text-align: right;
  font-size: 0.8em;
}

Connect to your SCOTT account and create the following table.

create table wordcard (
word varchar2(45),
definitions varchar2(4000),
constraint pk_wordcard primary key(word)
);

Add Spring JDBC, Oracle JDBC driver, Apache DBCP, and MyBatis with mybatis-spring.

pom.xml
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc6 -->
<dependency>
  <groupId>com.oracle.database.jdbc</groupId>
  <artifactId>ojdbc6</artifactId>
  <version>11.2.0.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
<dependency>
  <groupId>commons-dbcp</groupId>
  <artifactId>commons-dbcp</artifactId>
  <version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.1.0</version>
</dependency>

commons-dbcp is a database connection pool provided by Apache.
Since ojdbc6.jar is a JDBC 4 driver, we need the DBCP 1.4 version.
See https://dlcdn.apache.org//commons/dbcp/

Add datasource and SqlSessionFactoryBean to the spring configuration.

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
    destroy-method="close">
  <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
  <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" />
  <property name="username" value="scott" />
  <property name="password" value="tiger" />
  <property name="maxActive" value="100" />
  <property name="maxWait" value="1000" />
  <property name="poolPreparedStatements" value="true" />
  <property name="defaultAutoCommit" value="true" />
  <property name="validationQuery" value=" SELECT 1 FROM DUAL" />
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

If you are using commons-dbcp2, you need to change the maxActive and maxWait parameters to maxTotal and maxWaitMillis.

Java Beans
src/main/java/net/java_school/english/WordCard.java

WordCard.java
package net.java_school.english;

public class WordCard {
  private String word;
  private String definitions;

  public WordCard() {}

  public WordCard(String word, String definitions) {
    this.word = word;
    this.definitions = definitions;
  }

  public int getWord() {
    return word;
  }

  public void setWord(String word) {
    this.word = word;
  }

  public String getDefinitions() {
    return definitions;
  }

  public void setDefinitions(String definitions) {
    this.definitions = definitions;
  }
}

Mapper Interface
It is better to put the mapper interface in a separate package from other Spring components.

src/main/java/net/java_school/mybatis/WordMapper.java

WordMapper.java
package net.java_school.mybatis;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

public interface WordMapper {
  @Insert("INSERT INTO wordcard VALUES (#{word}, #{definitions})")
  public void insert(@Param("word") String word, @Param("definitions") String definitions);

  @Select("select * from (select * from wordcard order by dbms_random.value) where rownum = 1")
  public WordCard selectOne();
}

Add the mapper interface to the spring configuration using MapperFactoryBean.

<bean id="wordMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="net.java_school.mybatis.WordMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

Service

Modify the WordCardService class to an interface.

src/main/java/net/java_school/english/WordCardService.java

WordCardService.java
package net.java_school.english;

public interface WordCardService {
  public void add(String word, String definitions);
  public WordCard getWordCard();
}

src/main/java/net/java_school/english/WordCardServiceImpl.java

WordCardServiceImpl.java
package net.java_school.english;

import net.java_school.mybatis.WordMapper;

import org.springframework.stereotype.Service;

@Service
public class WordCardServiceImpl implements WordCardService {

  private WordMapper wordMapper;

  public WordCardServiceImpl(WordMapper wordMapper) {
    this.wordMapper = wordMapper;
  }

  @Override
  public void add(String content) {
    wordMapper.insert(content);
  }

  @Override
  public WordCard getWordCard() {
    return wordMapper.selectOne(); 
}
<bean id="wordCardService" class="net.java_school.english.WordCardServiceImpl">
  <constructor-arg ref="wordMapper" />
</bean>

Controller

src/main/java/net/java_school/english/HomeController.java

HomeController.java
package net.java_school.english;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class HomeController {

  private WordCardService wordCardService;

  public HomeController(WordCardService wordCardService) {
    this.wordCardService = wordCardService;
  }
  
  @GetMapping("/")
  public String homepage(Model model) {
    WordCard wordCard = wordCardService.getWordCard();
    model.addAttribute("wordCard", wordCard);
    return "index";
  }

  @PostMapping("/")
  public String add(String word, String definitions) {
    wordCardService.add(word, definitions);
    return "redirect:/";
  }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<mvc:resources mapping="/resources/**" location="/resources/" />

<mvc:annotation-driven />

<bean id="internalResourceViewResolver" 
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
  <property name="prefix" value="/WEB-INF/views/" />
  <property name="suffix" value=".jsp" />
</bean>

<!-- <bean id="wordCardDao" class="net.java_school.english.WordCardDao" /> -->

<bean id="wordMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="net.java_school.mybatis.WordMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

<bean id="wordCardService" class="net.java_school.english.WordCardServiceImpl">
  <constructor-arg ref="wordMapper" />
</bean>

<bean id="homeController" class="net.java_school.english.HomeController">
  <constructor-arg ref="wordCardService" />
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
    destroy-method="close">
  <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
  <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" />
  <property name="username" value="scott" />
  <property name="password" value="tiger" />
  <property name="maxActive" value="100" />
  <property name="maxWait" value="1000" />
  <property name="poolPreparedStatements" value="true" />
  <property name="defaultAutoCommit" value="true" />
  <property name="validationQuery" value=" SELECT 1 FROM DUAL" />
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

</beans>

Build and Deploy
mvn clean compile war:inplace
Test
http://localhost:8080

Logging

See https://mybatis.org/mybatis-3/logging.html

Add the apache commons-logging and log4j2 libraries to dependencies in pom.xml.

<!-- Logging -->                
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-api</artifactId>
  <version>2.19.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.19.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-jcl -->
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-jcl</artifactId>
  <version>2.19.0</version>
</dependency>

Create a log4j2 configuration file with the name log4j2.xml in the src/main/resources/ folder.

src/main/resources/log4j2.xml

log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<Configuration>
  <Appenders>
    <File name="WordCard" fileName="WordCard.log" append="false">
      <PatternLayout pattern="%t %-5p %c{2} - %m%n" />
    </File>
    <Console name="STDOUT" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />
    </Console>
  </Appenders>
  <Loggers>
    <Logger name="net.java_school" level="DEBUG">
      <AppenderRef ref="WordCard" />
    </Logger>
    <Root level="INFO">
      <AppenderRef ref="STDOUT" />
    </Root>
  </Loggers>
</Configuration>

Run mvn compile war:inplace to compile and check the log message.

Mapper XML Files
MyBatis provides a way to separate queries into XML files. This file is the Mapper XML file.

So far, we have not created a MyBatis configuration file.
If you put the Mapper XML file and the mapper interface on the same classpath, you don't need a MyBatis configuration file.
See https://mybatis.org/spring/mappers.html#register

Create a Mapper XML file named WordMapper.xml in the src/main/resources/net/java_school/mybatis folder.

src/main/resources/net/java_school/mybatis/WordMapper.xml

WordMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="net.java_school.mybatis.WordMapper">

  <insert id="insert">
    insert into wordcard values (#{word}, #{definitions})
  </insert>

  <select id="selectOne" resultType="net.java_school.english.WordCard">
    select *
    from
    (select * from wordcard order by dbms_random.value)
    where rownum = 1
  </select>
	
</mapper>

Using the typeAlias element of the MyBatis configuration file can shorten resultType="net.java_school.english.WordCard" to resultType="WordCard" in the Mapper XML file.
See https://mybatis.org/mybatis-3/sqlmap-xml.html

<-- MyBatis XML Configuration -->
<typeAlias type="net.java_school.english.WordCard" alias="WordCard"/>

Proceed without creating a MyBatis configuration file since only one Java Bean.

Remove queries from the WordMapper interface.

WordMapper.java
package net.java_school.mybatis;

import net.java_school.english.WordCard;

import org.apache.ibatis.annotations.Param;

public interface WordMapper {
  public void insert(@Param("word") String word, @Param("definitions") String definitions);
  public WordCard selectOne();
}

Spring Auto Scan

You can use Spring's auto-scan to reduce configuration file content.
<context:component-scan /> scans Spring's components like Controllers, services, and Repositories.
<mybatis:scan /> scans mapper instances.

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

<mvc:resources mapping="/resources/**" location="/resources/" />

<mvc:annotation-driven />

<context:component-scan base-package="net.java_school.english" />

<mybatis:scan base-package="net.java_school.mybatis" />

<bean id="internalResourceViewResolver" 
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
  <property name="prefix" value="/WEB-INF/views/" />
  <property name="suffix" value=".jsp" />
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
    destroy-method="close">
  <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
  <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" />
  <property name="username" value="java" />
  <property name="password" value="school" />
  <property name="maxActive" value="100" />
  <property name="maxWait" value="1000" />
  <property name="poolPreparedStatements" value="true" />
  <property name="defaultAutoCommit" value="true" />
  <property name="validationQuery" value=" SELECT 1 FROM DUAL" />
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

</beans>

<context:component-scan /> lets you to use @Autowired annotation.

HomeController.java
import org.springframework.beans.factory.annotation.Autowired;

@Controller
public class HomeController {

  @Autowired
  private WordCardService wordCardService;

  /* remove constructors
  public HomeController(WordCardService wordCardService) {
    this.wordCardSerivce = wordCardService;
  }
  */

  //..omit..
}
WordCardServiceImpl.java
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class WordCardServiceImpl implements WordCardService {

  @Autowired
  private WordMapper wordMapper;

  /* reomve constructors
  public WordCardServiceImpl(WordMapper wordMapper) {
    this.wordMapper = wordMapper;
  }
  */

  //..omit..
}

Java Based Configuration

Create the following java file.

AppConfig.java
package net.java_school.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class AppConfig implements WebMvcConfigurer {
  //TODO
}

Complete the AppConfig.java file by referring to the following.

<mvc:annotation-driven />

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
<mvc:resources mapping="/resources/**"
    location="/resources/"
    cache-period="31556926" />

@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/resources/**")
            .addResourceLocations("/resources/")
            .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)));
  }
}
<context:component-scan 
    base-package="net.java_school.english" />

@Configuration
@EnableWebMvc
@ComponentScan("net.java_school.english")
public class AppConfig implements WebMvcConfigurer {
<mybatis:scan base-package="net.java_school.mybatis" />

@Configuration
@EnableWebMvc
@ComponentScan("net.java_school.english")
@MapperScan("net.java_school.mybatis")
public class AppConfig implements WebMvcConfigurer {
<bean id="internalResourceViewResolver" 
      class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/" />
  <property name="suffix" value=".jsp" />
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
</bean>

@Bean
public ViewResolver configureViewResolver() {
  InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
  viewResolver.setPrefix("/WEB-INF/views/");
  viewResolver.setSuffix(".jsp");
  viewResolver.setViewClass(JstlView.class);
  return viewResolver;
} 
<bean id="dataSource"
    class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName"
    value="oracle.jdbc.driver.OracleDriver" />
  <property name="url"
    value="jdbc:oracle:thin:@localhost:1521:XE" />
  <property name="username" value="scott" />
  <property name="password" value="tiger" />
  <property name="maxActive" value="100" />
  <property name="maxWait" value="1000" />
  <property name="poolPreparedStatements" value="true" />
  <property name="defaultAutoCommit" value="true" />
  <property name="validationQuery" value=" SELECT 1 FROM DUAL" />
</bean>

<bean id="sqlSessionFactory"
    class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

@Bean(destroyMethod = "close")
public DataSource dataSource() {
  BasicDataSource dataSource = new BasicDataSource();
  dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
  dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:XE");
  dataSource.setUsername("scott");
  dataSource.setPassword("tiger");
  dataSource.setMaxActive(100);
  dataSource.setMaxWait(1000);
  dataSource.setPoolPreparedStatements(true);
  dataSource.setDefaultAutoCommit(true);
  dataSource.setValidationQuery("SELECT 1 FROM DUAL");
  return dataSource;
}

@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
  SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
  sessionFactory.setDataSource(dataSource());
  return sessionFactory.getObject();
}
AppConfig.java
package net.java_school.config;

import java.time.Duration;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
@EnableWebMvc
@ComponentScan("net.java_school.english")
@MapperScan("net.java_school.mybatis")
public class AppConfig implements WebMvcConfigurer {

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/resources/**")
            .addResourceLocations("/resources/")
            .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)));
  }

  @Bean
  public ViewResolver configureViewResolver() {
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    viewResolver.setPrefix("/WEB-INF/views/");
    viewResolver.setSuffix(".jsp");
    viewResolver.setViewClass(JstlView.class);
    return viewResolver;
  }

  @Bean(destroyMethod = "close")
  public DataSource dataSource() {
    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
    dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:XE");
    dataSource.setUsername("scott");
    dataSource.setPassword("tiger");
    dataSource.setMaxActive(100);
    dataSource.setMaxWait(1000);
    dataSource.setPoolPreparedStatements(true);
    dataSource.setDefaultAutoCommit(true);
    dataSource.setValidationQuery("SELECT 1 FROM DUAL");

    return dataSource;
  }

  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(dataSource());
    return sessionFactory.getObject();
  }
}

Create a Java-based configuration file to replace web.xml.

src/main/java/net/java_school/config/MyWebAppInitializer.java

MyWebAppInitializer.java
package net.java_school.config;

import javax.servlet.Filter;

import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return null;
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { AppConfig.class };
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }

  @Override
  protected Filter[] getServletFilters() {
    return new Filter[] { new HiddenHttpMethodFilter() };
  }
}

You can also set it as below.
See https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet-context-hierarchy

package net.java_school.config;

import javax.servlet.Filter;

import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] { AppConfig.class };
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return null;
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }

  @Override
  protected Filter[] getServletFilters() {
    return new Filter[] { new HiddenHttpMethodFilter() };
  }
}

Because Java-based configuration cannot map error pages, you better leave a minimal web.xml file for later error page mapping.
See https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-customer-servlet-container-error-page

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0">

</web-app>
References