Building Spring MVC with Maven
This article assumes that your workspace is C:\www.
Generating archetype
C:\ Command PromptC:\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': spring-bbs Define value for property 'version' 1.0-SNAPSHOT: : ↵ Define value for property 'package' net.java_school: : ↵ Confirm properties configuration: groupId: net.java_school artifactId: spring-bbs version: 1.0-SNAPSHOT package: net.java_school Y: : ↵
Maven creates the spring-bbs folder in C:\www through the build process. C:\www\spring-bbs is the root directory of your project. The src/main/webapp folder, just above WEB-INF, is the document base.
Create a Tomcat context file as follows.
ROOT.xml
<?xml version="1.0" encoding="UTF-8"?> <Context docBase="C:/www/spring-bbs/src/main/webapp" reloadable="true"> </Context>
Copy the ROOT.xml file to the CATALINA_HOME/conf/Catalina/localhost folder. After restart Tomcat, visit http://localhost:8080 to see the ROOT application is working.
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>spring-bbs</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>spring-bbs 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>spring-bbs</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>
Modify web.xml like below.
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>Spring BBS</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>spring-bbs</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-bbs</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <session-config> <session-timeout>-1</session-timeout> </session-config> <error-page> <error-code>404</error-code> <location>/WEB-INF/views/404.jsp</location> </error-page> <error-page> <error-code>500</error-code> <location>/WEB-INF/views/500.jsp</location> </error-page> </web-app>
Of the filters above, the one that executes the setCharacterEncoding("UTF-8") code on every request is essential for non-English websites. You should declare it before any other filters.
Since we set Spring MVC DispatcherServlet configuration file as /WEB-INF/spring/mvc.xml in web.xml, we need to create a spring configuration file named mvc.xml in /WEB-INF/spring/ folder.
mvc.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: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>
Design the homepage
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" /> <style> 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; } </style> </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
The src/main/java folder is the default maven folder.
package net.java_school.english; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class HomeController { @GetMapping("/") public String index() { return "index"; } }
@Controller and @GetMapping are Spring annotations used to pass information to Spring. @Controller placed above the class declaration tells Spring that the Java bean is a controller. @GetMapping("/") located above the controller method indicates that this method handles the GET "/" request. If it is the root application, that "/" will be http://localhost:8080/.
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" />
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
Visit http://localhost:8080
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 index(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 compile war:inplace
http://localhost:8080
MyBatis-Spring Example
We haven't covered the database yet.
Model 2 bulletin board uses JDBC as it is. But code that uses JDBC as-is is unproductive due to repetitive code.
Using the persistence framework--it uses JDBC as an internal source--reduces the amount of code you have to write.
MyBatis is a SQL mapping persistence framework.
MyBatis-Spring is an interlocking module for conveniently using MyBatis in Spring.
See https://mybatis.org/spring/ko/getting-started.html
Connect to your JAVA account and create the following table and sequence.
create table photo ( no number, content varchar2(4000) not null, constraint pk_photo primary key(no), constraint uq_photo unique(content) ); create sequence seq_photo increment by 1 start with 1;
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.
<?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: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="wordCardService" class="net.java_school.english.WordCardService"> <constructor-arg ref="wordCardDao" /> </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="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>
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/photostudio/Photo.java
Photo.java
package net.java_school.photostudio; public class Photo { private int no; private String content; public Photo() {} public Photo(int no, String content) { this.no = no; this.content = content; } public int getNo() { return no; } public void setNo(int no) { this.no = no; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
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/PhotoMapper.java
PhotoMapper.java
package net.java_school.mybatis; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Insert; public interface PhotoMapper { @Insert("INSERT INTO photo VALUES (seq_photo.nextval, #{content})") public void insert(@Param("content") String content); }
Add the mapper interface to the spring configuration using MapperFactoryBean.
<bean id="photoMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="net.java_school.mybatis.PhotoMapper" /> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
Service
src/main/java/net/java_school/photostudio/PhotoService.java
PhotoService.java
package net.java_school.photostudio; public interface PhotoService { public void add(String content); }
src/main/java/net/java_school/photostudio/PhotoServiceImpl.java
PhotoServiceImpl.java
package net.java_school.photostudio; import net.java_school.mybatis.PhotoMapper; import org.springframework.stereotype.Service; @Service public class PhotoServiceImpl implements PhotoService { private PhotoMapper photoMapper; public PhotoServiceImpl(PhotoMapper photoMapper) { this.photoMapper = photoMapper; } @Override public void add(String content) { photoMapper.insert(content); } }
<bean id="photoService" class="net.java_school.photostudio.PhotoServiceImpl"> <constructor-arg ref="photoMapper" /> </bean>
Controller
Add @RequestMapping("photo") annotation on the class declaration to create a controller that handles requests with "photo" appended to the ContextPath.
src/main/java/net/java_school/photostudio/PhotoController.java
PhotoController.java
package net.java_school.photostudio; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @Controller @RequestMapping("photo") public class PhotoController { private PhotoService photoService; public PhotoController(PhotoService photoService) { this.photoService = photoService; } @GetMapping public String index() { return "photo/index"; } @PostMapping public String add(String content) { photoService.add(content); return "redirect:/photo/?page=1"; } }
<bean id="photoController" class="net.java_school.photostudio.PhotoController"> <constructor-arg ref="photoService" /> </bean>
View
src/main/webapp/WEB-INF/views/photo/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>mybatis-spring Test</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="Keywords" content="MyBatis Spring Test" /> <meta name="Description" content="This is web app for mybatis-spring Test" /> <style> html, body { margin: 0; padding: 0; background-color: #FFF; font-family: "Liberation Sans", Helvetica, sans-serif; } </style> </head> <body> <form id="addForm" method="post"> <input type="text" name="content" /> <input id="submit" type="submit" value="Send" /> </form> </body> </html>
Test
mvn compile war:inplace
http://localhost:8080/photo
Paste the web image link into the text field and click the send button.
Connect to SQL*PLUS and check if there are any records in the table.
Logging
See https://mybatis.org/mybatis-3/ko/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. That is the default maven folder.
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration> <Configuration> <Appenders> <File name="A1" fileName="A1.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="A1" /> </Logger> <Root level="INFO"> <AppenderRef ref="STDOUT" /> </Root> </Loggers> </Configuration>
Run mvn compile war:inplace to compile and check the log message.
http-nio-8080-exec-14 DEBUG PhotoMapper.insert - ==> Preparing: INSERT INTO photo VALUES (seq_photo.nextval, ? http-nio-8080-exec-14 DEBUG PhotoMapper.insert - ==> Parameters: https://cdn.pixabay.com/house_720.jpg(String)
The right parenthesis is missing in the insert statement.
Add parentheses and compile again.
Check the log message.
http-nio-8080-exec-26 DEBUG PhotoMapper.insert - ==> Preparing: INSERT INTO photo VALUES (seq_photo.nextval, ?) http-nio-8080-exec-26 DEBUG PhotoMapper.insert - ==> Parameters: https://cdn.pixabay.com/house_720.jpg(String) http-nio-8080-exec-26 DEBUG PhotoMapper.insert - <== Updates: 1
XML Mapper
MyBatis provides a way to separate queries into XML files. This file is the XML mapper file.
이 파일이 XML 매퍼 파일이다.
So far, we have not created a MyBatis configuration file.
If you put the XML mapper file and the mapper interface on the same classpath, you don't need a MyBatis configuration file.
See https://mybatis.org/spring/ko/mappers.html#register
Create a PhotoMapper.xml mapper file in the src/main/resources/net/java_school/mybatis folder.
src/main/resources/net/java_school/mybatis/PhotoMapper.xml
PhotoMapper.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.PhotoMapper"> <insert id="insert"> INSERT INTO photo VALUES (seq_photo.nextval, #{content}) </insert> </mapper>
Remove the query from the PhotoMapper interface.
PhotoMapper.java
package net.java_school.mybatis; import org.apache.ibatis.annotations.Param; public interface PhotoMapper { public void insert(@Param("content") String content); }
Show saved images
Let's try to show four images per page.
Modify the contents of the body element of the index.jsp file as follows.
index.jsp
<div id="photos"> <img width="640" alt="p_1" src="https://cdn.pixabay.com/photo/2022/10/09/02/16/haunted-house-7508035_960_720.jpg" /> <img width="640" alt="p_2" src="https://cdn.pixabay.com/photo/2022/10/09/02/16/haunted-house-7508035_960_720.jpg" /> <img width="640" alt="p_3" src="https://cdn.pixabay.com/photo/2022/10/09/02/16/haunted-house-7508035_960_720.jpg" /> <img width="640" alt="p_4" src="https://cdn.pixabay.com/photo/2022/10/09/02/16/haunted-house-7508035_960_720.jpg" /> </div> <div id="paging"> <a href="?page=10" title="10">◁ back</a> <a href="?page=1" title="1">1</a> <a href="?page=10" title="10">...</a> <strong>11</strong> <a href="?page=12" title="12">12</a> <a href="?page=13" title="3">3</a> <a href="?page=14" title="4">4</a> <a href="?page=15" title="5">5</a> <a href="?page=16" title="6">6</a> <a href="?page=17" title="7">7</a> <a href="?page=18" title="8">8</a> <a href="?page=19" title="9">9</a> <a href="?page=20" title="10">10</a> <a href="?page=21" title="11">...</a> <a href="?page=407" title="407">407</a> <a href="?page=12" title="12">next▷ </a> <form id="addForm" method="post"> <input type="text" name="content" width="500" /> <input id="submit" type="submit" value="Send" /> </form> </div>
Add the following to the style element of the file.
#paging { width: 640px; float: left; font-size: 1em; } a:link { color: #2C80D0; text-decoration: none; } a:visited { color: #2C80D0; text-decoration: none; } a:active { color: #2C80D0; text-decoration: none; } a:hover { color: #2C80D0; text-decoration: underline; }
Add methods to the PhotoMapper interface to get the total number of records and items to display on the page.
PhotoMapper.java
package net.java_school.mybatis; import net.java_school.photostudio.Photo; import java.util.HashMap; import java.util.List; import org.apache.ibatis.annotations.Param; public interface PhotoMapper { public void insert(@Param("content") String content); public int selectCountOfPhotos();//Total Record Count public List<Photo> selectPhotos(HashMap<String, String> hashmap);//Itemsfor view }
PhotoMapper.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.PhotoMapper"> <insert id="insert"> NSERT INTO photo VALUES (seq_photo.nextval, #{content}) </insert> <select id="selectCountOfPhotos" resultType="int"> SELECT count(*) FROM photo </select> <select id="selectPhotos" parameterType="hashmap" resultType="net.java_school.photostudio.Photo"> SELECT no,content FROM ( SELECT rownum R,A.* FROM ( SELECT no,content FROM photo ORDER BY no DESC ) A ) WHERE R BETWEEN #{start} AND #{end} </select> </mapper>
Using the typeAlias element of the MyBatis configuration file can shorten resultType="net.java_school.photostudio.Photo" to resultType="Photo" in the XML mapper file.
See https://mybatis.org/mybatis-3/ko/sqlmap-xml.html
<-- MyBatis XML Configuration --> <typeAlias type="net.java_school.photostudio.Photo" alias="Photo"/>
Proceed without creating a MyBatis configuration file since only one Java Bean.
PhotoService.java
package net.java_school.photostudio; import java.util.List; public interface PhotoService { public void add(String content); public int getTotalRecordCount(); public List<Photo> getPhotos(Integer startRecord, Integer endRecord); }
PhotoServiceImpl.java
package net.java_school.photostudio; import java.util.List; import java.util.HashMap; import net.java_school.mybatis.PhotoMapper; public class PhotoServiceImpl implements PhotoService { private PhotoMapper photoMapper; public PhotoServiceImpl(PhotoMapper photoMapper) { this.photoMapper = photoMapper; } @Override public void add(String content) { photoMapper.insert(content); } @Override public int getTotalRecordCount() { return photoMapper.selectCountOfPhotos(); } @Override public List<Photo> getPhotos(Integer startRecord, Integer endRecord) { HashMap<String, String> hashmap = new HashMap<String, String>(); hashmap.put("start", startRecord.toString()); hashmap.put("end", endRecord.toString()); return photoMapper.selectPhotos(hashmap); } }
PhotoController.java
package net.java_school.photostudio; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import java.util.List; import java.util.Map; import java.util.HashMap; @Controller @RequestMapping("/photo") public class PhotoController { private PhotoService photoService; public PhotoController(PhotoService photoService) { this.photoService = photoService; } @PostMapping public String add(String content) { photoService.add(content); return "redirect:/photo/?page=1"; } private Map<String, Integer> getNumbersForPaging(int totalRecordCount, int page, int recordsPerPage, int pagesPerBlock) { Map<String, Integer> map = new HashMap<String, Integer>(); int totalPageCount = totalRecordCount / recordsPerPage; if (totalRecordCount % recordsPerPage != 0) totalPageCount++; int totalBlockCount = totalPageCount / pagesPerBlock; if (totalPageCount % pagesPerBlock != 0) totalBlockCount++; int block = page / pagesPerBlock; if (page % pagesPerBlock != 0) block++; int firstPage = (block - 1) * pagesPerBlock + 1; int lastPage = block * pagesPerBlock; int prevBlock = 0;//previus block's last page if (block > 1) prevBlock = firstPage - 1; int nextBlock = 0;//next blcok's first page if (block < totalBlockCount) nextBlock = lastPage + 1; if (block >= totalBlockCount) lastPage = totalPageCount; int listItemNo = totalRecordCount - (page - 1) * recordsPerPage; int startRecord = (page - 1) * recordsPerPage + 1; int endRecord = page * recordsPerPage; map.put("finalPage", totalPageCount); map.put("firstPage", firstPage); map.put("lastPage", lastPage); map.put("prevBlock", prevBlock); map.put("nextBlock", nextBlock); map.put("startRecord", startRecord); map.put("endRecord", endRecord); return map; } @GetMapping public String index(Integer page, Model model) { if (page == null) return "redirect:/photo/?page=1"; int recordsPerPage = 4; int pagesPerBlock = 10; int totalRecordCount = photoService.getTotalRecordCount(); Map<String, Integer> map = getNumbersForPaging(totalRecordCount, page, recordsPerPage, pagesPerBlock); Integer startRecord = map.get("startRecord"); Integer endRecord = map.get("endRecord"); List<Photo> photos = photoService.getPhotos(startRecord, endRecord); Integer prevBlock = map.get("prevBlock"); Integer nextBlock = map.get("nextBlock"); Integer firstPage = map.get("firstPage"); Integer lastPage = map.get("lastPage"); Integer finalPage = map.get("finalPage"); model.addAttribute("photos", photos); model.addAttribute("prevBlock", prevBlock); model.addAttribute("nextBlock", nextBlock); model.addAttribute("firstPage", firstPage); model.addAttribute("lastPage", lastPage); model.addAttribute("finalPage", finalPage); return "photo/index"; } }
View
To use JSTL, add the following to the index.jsp file.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
Use the JSTL code to set the passed data in the view.
index.jsp
<div id="photos"> <c:forEach var="photo" items="${photos }" varStatus="status"> <img width="640" alt="p_${photo.no }" src="${photo.content }" /> </c:forEach> </div> <div id="paging"> <c:if test="${param.page > 1 }"> <a href="?page=${param.page - 1 }" title="${param.page - 1}">◁ Back</a> </c:if> <c:if test="${prevBlock > 0}"> <a href="?page=1" title="1">1</a> <a href="?page=${prevBlock }" title="${prevBlock }">...</a> </c:if> <c:forEach var="i" begin="${firstPage }" end="${lastPage }" varStatus="status"> <c:choose> <c:when test="${param.page == i}"> <strong>${i }</strong> </c:when> <c:otherwise> <a href="?page=${i }" title="${i }">${i }</a> </c:otherwise> </c:choose> </c:forEach> <c:if test="${nextBlock > 0 }"> <a href="?page=${nextBlock }" title="${nextBlock }">...</a> <a href="?page=${finalPage }" title="${finalPage }">${finalPage }</a> </c:if> <c:if test="${param.page < finalPage }"> <a href="?page=${param.page + 1 }" title="${param.page + 1 }">Next▷ </a> </c:if> <form id="addForm" method="post"> <input type="text" name="content" width="500" /> <input id="submit" type="submit" value="Send" /> </form> </div>
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.
mvc.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" 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:annotation-driven /> <context:component-scan base-package="net.java_school.english, net.java_school.photostudio" /> <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.
PhotoController.java
import org.springframework.beans.factory.annotation.Autowired; @Controller public class PhotoController { @Autowired private PhotoService photoService; /* Remove the Constructor. public PhotoController(PhotoService photoService) { this.photoService = photoService; } */ //..omit.. }
PhotoServiceImpl.java
import org.springframework.stereotype.Service; import org.springframework.beans.factory.annotation.Autowired; @Service public class PhotoServiceImpl implements PhotoService { @Autowired private PhotoMapper photoMapper; /* Remove a Constructor public PhotoServiceImpl(PhotoMapper photoMapper) { this.photoMapper = photoMapper; } */ //..omit.. }
Add annotations to WordCardDao, WordCardService, and HomeController so that <context:component-scan /> can scan them.
src/main/java/net/java_school/english/WordCardDao.java
WordCardDao.java
import org.springframework.stereotype.Repository; @Repository public class WordCardDao { //..omit.. }
src/main/java/net/java_school/english/WordCardService.java
WordCardService.java
import org.springframework.stereotype.Service; import org.springframework.beans.factory.annotation.Autowired; @Service public class WordCardService { @Autowired private WordCardDao wordCardDao; //..omit.. }
src/main/java/net/java_school/english/HomeController.java
HomeController.java
import org.springframework.beans.factory.annotation.Autowired; @Controller public class HomeController { @Autowired private WordCardService wordCardService; //..omit.. }
Set up the project in eclipse
Start Eclipse and select your workspace as C:\www. In the Project Explorer view, use the right mouse button to display context menus. Import the spring-bbs project into Eclipse.
If the pom.xml file changes, you need to synchronize pom.xml with Eclipse.
References
- http://stackoverflow.com/questions/14004308/spring-autowiring-not-able-to-hit-my-dao-class-method
- http://static.springsource.org/spring/docs/current/spring-framework-reference/pdf/
- Guide to naming conventions on groupId, artifactId and version
- 4 ways to use the WAR Plugin
- https://github.com/spring-projects/spring-mvc-showcase/blob/master/pom.xml
- https://stackoverflow.com/questions/793983/jsp-el-expression-is-not-evaluated