10.1. Tutorial (Todo Application)¶
Index
- Introduction
- Description of application to be created
- Environment creation
- Creation of Todo application
- Creating infrastructure layer with a Database access
- In the end…
- Appendix
10.1.1. Introduction¶
10.1.1.1. Points to study in this tutorial¶
- Basic application development using TERASOLUNA Server Framework for Java (5.x)
- Configuration of Maven and STS(Eclipse) project
- Way of development using application layering of TERASOLUNA Server Framework for Java (5.x).
10.1.1.2. Target readers¶
- Basic knowledge of Spring DI and AOP required
- Web application development using Servlet/JSP
- SQL knowledge
10.1.1.3. Verification environment¶
In this tutorial, operations are verified on following environment. In case of implementation on other environment, perform appropriate setting based on this guideline.
Type | Name |
---|---|
OS | Windows 7 |
JVM | Java 1.8 |
IDE | Spring Tool Suite 3.6.4.RELEASE (Onwards referred as [STS]) |
Build Tool | Apache Maven 3.3.9 (Onwards referred as [Maven]) |
Application Server | Pivotal tc Server Developer Edition v3.1 (Enclosed in STS) |
Web Browser | Google Chrome 46.0.2490.80 m |
10.1.2. Description of application to be created¶
10.1.2.1. Overview of the application¶
Application to manage TODO list is to be developed. TODO list display, TODO registration, TODO completion and TODO deletion can be performed.
10.1.2.2. Business requirements of application¶
Business requirement of the application is as follows.
RuleID | Description |
---|---|
B01 | Only up to 5 incomplete TODO records can be registered |
B02 | For TODOs which are already completed, “TODO Complete” processing cannot be done. |
Note
This application is for learning purpose only. It is not suitable as a real todo management application.
10.1.2.3. Screen transition of application¶
Processing specification and screen transition of an application is as follows.
Sr.No. | Process name | HTTP method | URL | Remark |
---|---|---|---|---|
1 | Show all TODO | - | /todo/list | |
2 | Create TODO | POST | /todo/create | Redirect to Show all TODO after creation is completed |
3 | Finish TODO | POST | /todo/finish | Redirect to Show all TODO after completion process |
4 | Delete TODO | POST | /todo/delete | Redirect to Show all TODO after deletion is completed |
10.1.2.3.1. Show all TODO¶
- Display all records of TODO
- Provide
Finish
andDelete
buttons for incomplete TODO - Strike-through the completed records of TODO
- Display only record name of TODO
10.1.2.3.2. Create TODO¶
- Save TODO sent from the form
- Record name of TODO should be between 1 - 30 characters
- When Business requirements of application B01 is not fulfilled, business exception with error code E001 is thrown
- Display “Created successfully!” at the transited screen when the creation process is successful.
10.1.2.3.3. Finish TODO¶
- For the TODOs corresponding to
todoId
which is received from the form object, change the status tocompleted
. - When the corresponding TODO does not exist, resource not found exception with error code E404 is thrown
- When Business requirements of application B02 is not fulfilled, business exception with error code E002 is thrown
- Display “Finished successfully!” at the transited screen when the finishing process is successful.
10.1.2.3.4. Delete TODO¶
- Delete TODO corresponding to
todoId
sent from the form - When the corresponding TODO does not exist, resources undetected exception with error code E404 is thrown
- Display “Deleted successfully!” at the transited screen when the deletion process is successful.
10.1.2.4. Error message list¶
Define below 3 error messages.
Error code | Message | Parameter to be replaced |
---|---|---|
E001 | [E001] The count of un-finished Todo must not be over {0}. | {0}… max unfinished count |
E002 | [E002] The requested Todo is already finished. (id={0}) | {0}… todoId |
E404 | [E404] The requested Todo is not found. (id={0}) | {0}… todoId |
10.1.3. Environment creation¶
In this tutorial, as RepositoryImpl implementation of the infrastructure layer,
- In-memory implementation of RepositoryImpl using
java.util.Map
without using the database - RepositoryImpl access the database using MyBatis3
- RepositoryImpl access the database using Spring Data JPA
3 types are prepared. Select anyone depending upon the use.
Under this tutorial, First, try the in-memory implementation followed by select myBatis3 or Spring Data JPA.
10.1.3.1. Project creation¶
First, create a blank project for implementation of infrastructure layer using mvn archetype:generate
.
This is a procedure to create a blank project using the Windows command prompt.
Note
If internet connection is accessed through proxy server, In order to perform the following tasks, necessary STS proxy settings and Maven proxy setting needs to be done.
Tip
If mvn archetype:generate
executes on Bash, it can be executed by replacing the ^
with \
.
mvn archetype:generate -B\ -DarchetypeGroupId=org.terasoluna.gfw.blank\ -DarchetypeArtifactId=terasoluna-gfw-web-blank-archetype\ -DarchetypeVersion=5.3.1.RELEASE\ -DgroupId=todo\ -DartifactId=todo\ -Dversion=1.0.0-SNAPSHOT
10.1.3.1.1. Creating O/R Mapper independent blank project¶
If you want to create a project for RepositoryImpl using java.util.Map
(without accessing the database),
run the following command to create O/R Mapper independent blank project in command prompt. If you read through this tutorial in consecutive order, first of all, create a project in this way.
mvn archetype:generate -B^
-DarchetypeGroupId=org.terasoluna.gfw.blank^
-DarchetypeArtifactId=terasoluna-gfw-web-blank-archetype^
-DarchetypeVersion=5.3.1.RELEASE^
-DgroupId=todo^
-DartifactId=todo^
-Dversion=1.0.0-SNAPSHOT
10.1.3.1.2. Creating blank project for MyBatis3¶
If you want to create a project for RepositoryImpl to access the database using MyBatis3, run the following command to create a blank project for the MyBatis3. This way to create a project is to be done in Creating infrastructure layer with MyBatis3.
mvn archetype:generate -B^
-DarchetypeGroupId=org.terasoluna.gfw.blank^
-DarchetypeArtifactId=terasoluna-gfw-web-blank-mybatis3-archetype^
-DarchetypeVersion=5.3.1.RELEASE^
-DgroupId=todo^
-DartifactId=todo^
-Dversion=1.0.0-SNAPSHOT
10.1.3.1.3. Creating blank project for JPA¶
If you want to create a project for RepositoryImpl to access the database using Spring Data JPA, run the following command to create a blank project for the JPA. This way to create a project is to be done in Creating infrastructure layer with Spring Data JPA.
mvn archetype:generate -B^
-DarchetypeGroupId=org.terasoluna.gfw.blank^
-DarchetypeArtifactId=terasoluna-gfw-web-blank-jpa-archetype^
-DarchetypeVersion=5.3.1.RELEASE^
-DgroupId=todo^
-DartifactId=todo^
-Dversion=1.0.0-SNAPSHOT
10.1.3.2. Project import¶
Import created blank project into STS.
Select the archetype created project from STS menu [File] -> [Import] -> [Maven] -> [Existing Maven Projects] -> [Next].
Click on [Finish] by selecting C:\work\todo
in Root Directory and selecting pom.xml of todo in Projects.
When the import is completed, project is displayed in the Package Explorer as follows.
Note
If the build error occurs after the import, it can be removed by Right click on the project name in Package Explorer and select [Maven] -> [Update Project…] -> [OK] .
Tip
For better visibility, Package Presentation must be changed to [Hierarchical] from default [Flat].
Click [View Menu] (The right edge of the down arrow) of the Package Explorer and select [Package Presentation] -> [Hierarchical].
It can be displayed as follows if Package Presentation changed into [Hierarchical].
Warning
H2 Database has been defined as a dependency in O/R Mapper type blank project but, this setting is done to create a simple application easily therefore it is not intended to use in the actual application development.
The following definitions shall be removed while performing the actual application development.
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
note:
In the above setting example, since it is assumed that the dependent library version is managed by the parent project terasoluna-gfw-parent , specifying the version in pom.xml is not necessary.
The above dependent library used by terasoluna-gfw-parent is defined by \ `Spring IO Platform <http://platform.spring.io/platform/>`_\ .
10.1.3.3. Project configuration¶
Below is the structure of the project to be created in this tutorial.
Note
It had been recommended to use a multi-project structure in [Project Structure] section of previous chapter but, in this tutorial, a single project configuration is used because it focuses on ease of learning. However, when in a real project, multi project configuration is strongly recommended.
However, when in a real project, multi project configuration is strongly recommended.
For creating multi-project, Refer [Create Web application development project].
[Configuration of blank project created for MyBatis3]
src
└main
├java
│ └todo
│ ├ app
│ │ └todo
│ └domain
│ ├model
│ ├repository
│ │ └todo
│ └service
│ └todo
├resources
│ ├META-INF
│ │ ├mybatis ... (8)
│ │ └spring
│ └todo
│ └domain
│ └repository ... (9)
│ └todo
└wepapp
└WEB-INF
└views
Sr. No. | Description |
---|---|
(8)
|
Directory that contains the MyBatis configuration files. |
(9)
|
Directory that contains the Mapper of MyBatis describing the SQL. In this tutorial, create a directory for storing the Mapper file of Repository for Todo object. |
[Configuration of blank project created for JPA, O/R Mapper independent blank project]
src
└main
├java
│ └todo
│ ├ app ... (1)
│ │ └todo
│ └domain ... (2)
│ ├model ... (3)
│ ├repository ... (4)
│ │ └todo
│ └service ... (5)
│ └todo
├resources
│ └META-INF
│ └spring ... (6)
└wepapp
└WEB-INF
└views ... (7)
Sr. No. | Description |
---|---|
(1)
|
Packages for storing application layer classes. In this tutorial, creating package for storing the Todo business classes. |
(2)
|
Packages for storing domain layer class. |
(3)
|
Packages for storing Domain Object. |
(4)
|
Packages for storing Repository In this tutorial, creating package for storing the Todo object (Domain Object) Repository. |
(5)
|
Packages for storing Service. In this tutorial, creating package for storing the Todo business services. |
(6)
|
Directory that contains the Spring configuration files. |
(7)
|
Directory for storing jsp. |
10.1.3.4. Confirmation of configuration file¶
Many settings those are required in advanced tutorial already done in the created blank project.
If only the implementation of the tutorial is concern, understanding of these settings are not required but it is recommended that you understand what settings are necessary to run an application.
For description of the required configuration (settings file) to run an application, Refer [Description of the configuration file].
Note
If you are creating a Todo application for familiarizing with the system, you may skip the confirmation of configuration file but suggest to read this after creating the Todo application.
10.1.3.5. Operation verification of the project¶
Before starting the development of Todo application, verify the project operation.
Since the implementation of the Controller and JSP for displaying the top page are provided in the blank project, it is possible to check the operation by displaying the top page.
The following implementation has been done in the Controller(src/main/java/todo/app/welcome/HelloController.java
),
provided in the blank project.
package todo.app.welcome;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the application home page.
*/
// (1)
@Controller
public class HelloController {
// (2)
private static final Logger logger = LoggerFactory
.getLogger(HelloController.class);
/**
* Simply selects the home view to render by returning its name.
*/
// (3)
@RequestMapping(value = "/", method = {RequestMethod.GET, RequestMethod.POST})
public String home(Locale locale, Model model) {
// (4)
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,
DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
// (5)
model.addAttribute("serverTime", formattedDate);
// (6)
return "welcome/home";
}
}
Sr. No. | Description |
---|---|
(1)
|
In order to make the Controller as component-scan target, attach
@Controller annotation to class level. |
(2)
|
Generating logger for outputting the log at (4).
Logger impmements the logback but, the API
org.slf4j.Logger of SLF4J is used. |
(3)
|
Set mapping methods for accessing the
"/" (root) using @RequestMapping annotation. |
(4)
|
Outputting info level log for notifying that the method is called.
|
(5)
|
For displaying date on screen set date as
"serverTime" attribute name to the Model. |
(6)
|
Return
"welcome/home" as view name. Using ViewResolver settings, WEB-INF/views/welcome/home.jsp is called. |
The following implementation has been done in the JSP(src/main/webapp/WEB-INF/views/welcome/home.jsp
),
provided in the blank project.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div id="wrapper">
<h1>Hello world!</h1>
<!-- (7) -->
<p>The time on the server is ${serverTime}.</p>
</div>
</body>
</html>
Sr. No. | Description |
---|---|
(7)
|
Display
"serverTime" passed from Controller in Model.Here, XSS measures are done but always performs the XSS measures for displaying the user input values by
f:h() function. |
Right click on project and select [Run As] -> [Run on Server].
Select [Next] after selecting AP server (Pivotal tc Server Developer Edition v3.1).
Verify that todo is included in [Configured] and click [Finish] to start the server.
When started, log shown as below will be output.
For "/"
path, it is understood that hello method of todo.app.welcome.HelloController
is mapped.
date:2016-02-17 11:25:30 thread:localhost-startStop-1 X-Track: level:INFO logger:o.springframework.web.servlet.DispatcherServlet message:FrameworkServlet 'appServlet': initialization started
date:2016-02-17 11:25:31 thread:localhost-startStop-1 X-Track: level:DEBUG logger:o.t.gfw.web.codelist.CodeListInterceptor message:registered codeList : []
date:2016-02-17 11:25:31 thread:localhost-startStop-1 X-Track: level:INFO logger:o.s.w.s.m.m.a.RequestMappingHandlerMapping message:Mapped "{[/],methods=[GET || POST],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String todo.app.welcome.HelloController.home(java.util.Locale,org.springframework.ui.Model)
date:2016-02-17 11:25:31 thread:localhost-startStop-1 X-Track: level:INFO logger:o.s.w.s.m.m.a.RequestMappingHandlerAdapter message:Looking for @ControllerAdvice: WebApplicationContext for namespace 'appServlet-servlet': startup date [Wed Feb 17 11:25:30 JST 2016]; parent: Root WebApplicationContext
date:2016-02-17 11:25:32 thread:localhost-startStop-1 X-Track: level:INFO logger:o.s.w.s.m.m.a.RequestMappingHandlerAdapter message:Looking for @ControllerAdvice: WebApplicationContext for namespace 'appServlet-servlet': startup date [Wed Feb 17 11:25:30 JST 2016]; parent: Root WebApplicationContext
date:2016-02-17 11:25:32 thread:localhost-startStop-1 X-Track: level:INFO logger:o.s.web.servlet.handler.SimpleUrlHandlerMapping message:Mapped URL path [/**] onto handler 'org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#0'
date:2016-02-17 11:25:32 thread:localhost-startStop-1 X-Track: level:INFO logger:o.s.web.servlet.handler.SimpleUrlHandlerMapping message:Mapped URL path [/resources/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
date:2016-02-17 11:25:33 thread:localhost-startStop-1 X-Track: level:INFO logger:o.springframework.web.servlet.DispatcherServlet message:FrameworkServlet 'appServlet': initialization completed in 2826 ms
If http://localhost:8080/todo is accessed in browser, following is displayed.
If you confirm the console,
- TRACE log of
TraceLoggingInterceptor
provided by the common library - INFO log implemented in the Controller
are the log output.
date:2016-02-17 11:25:35 thread:tomcat-http--11 X-Track:b49b630274974bffbcd9e8d13261f6a7 level:TRACE logger:o.t.gfw.web.logging.TraceLoggingInterceptor message:[START CONTROLLER] HelloController.home(Locale,Model)
date:2016-02-17 11:25:35 thread:tomcat-http--11 X-Track:b49b630274974bffbcd9e8d13261f6a7 level:INFO logger:todo.app.welcome.HelloController message:Welcome home! The client locale is ja_JP.
date:2016-02-17 11:25:35 thread:tomcat-http--11 X-Track:b49b630274974bffbcd9e8d13261f6a7 level:TRACE logger:o.t.gfw.web.logging.TraceLoggingInterceptor message:[END CONTROLLER ] HelloController.home(Locale,Model)-> view=welcome/home, model={serverTime=2016/02/17 11:25:35 JST}
date:2016-02-17 11:25:35 thread:tomcat-http--11 X-Track:b49b630274974bffbcd9e8d13261f6a7 level:TRACE logger:o.t.gfw.web.logging.TraceLoggingInterceptor message:[HANDLING TIME ] HelloController.home(Locale,Model)-> 97,346,576 ns
Note
The TraceLoggingInterceptor
outputs start and end log of the Controller. While ending, the information of View
and Model
as well as processing time are output.
10.1.4. Creation of Todo application¶
- Domain layer (+ Infrastructure layer)
- Domain Object creation
- Repository creation
- RepositoryImpl creation
- Service creation
- Application layer
- Controller creation
- Form creation
- View creation
About the creation of RepositoryImpl, implementation is differ depending on the type of the selected infrastructure layer.
Here, In-memory implemented RepositoryImpl is created using java.util.Map
without using the database is explained.
If you want to use database, create Todo application by referring the [Creating infrastructure layer with a Database access] content.
10.1.4.1. Creation of Domain layer¶
10.1.4.1.1. Creation of Domain Object¶
Create Domain object.
Right click on the Package Explorer, select -> [New] -> [Class] -> [New Java Class] dialog box appears,
Sr. No. Item Input value 1 Package todo.domain.model
2 Name Todo
3 Interfaces java.io.Serializable
and click [Finish] after entering above information.
Created class stored in the following directory.
Add following properties in created class.
- ID = todoId
- Title = todoTitle
- Completion flag = finished
- Created on = createdAt
package todo.domain.model;
import java.io.Serializable;
import java.util.Date;
public class Todo implements Serializable {
private static final long serialVersionUID = 1L;
private String todoId;
private String todoTitle;
private boolean finished;
private Date createdAt;
public String getTodoId() {
return todoId;
}
public void setTodoId(String todoId) {
this.todoId = todoId;
}
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
public boolean isFinished() {
return finished;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
}
Tip
Getter/Setter methods can be generated automatically by using STS feature. After defining fields, right click on editor and select [Source] -> [Generate Getter and Setters…]
Click [OK] after selecting all other than serialVersionUID.
10.1.4.1.2. Repository creation¶
Create TodoRepository
interface.
If you want use database, create Repository by referring the [Creating infrastructure layer with a Database access] content.
Right click on the Package Explorer, select -> [New] -> [Interface] -> [New Java Interface] dialog box appears,
Sr. No. Item Input value 1 Package todo.domain.repository.todo
2 Name TodoRepository
and click [Finish] after entering above information.
Created Interface stored in the following directory.
Define following CRUD operation methods pertaining to this application in created interface.
- Fetch 1 record of TODO = findOne
- Fetch all records of TODO = findAll
- Create 1 record of TODO = create
- Update 1 record of TODO = update
- Delete 1 record of TODO = delete
- Fetch record count of completed TODO = countByFinished
package todo.domain.repository.todo;
import java.util.Collection;
import todo.domain.model.Todo;
public interface TodoRepository {
Todo findOne(String todoId);
Collection<Todo> findAll();
void create(Todo todo);
boolean update(Todo todo);
void delete(Todo todo);
long countByFinished(boolean finished);
}
Note
Here, to improve versatility of TodoRepository
, instead of (long countFinished()
) method for [Fetch completed record count],
(long countByFinished(boolean)
) method for [record count having xx completion status ] is defined.
If pass true
as an argument to long countByFinished(boolean)
, [completed record count] and
if pass false
as an argument, [not completed record count] can be fetched by specification.
10.1.4.1.3. Creation of RepositoryImpl (Infrastructure layer)¶
Here, In-memory implemented RepositoryImpl is created using java.util.Map
for simplification.
If you want use database, create RepositoryImpl by referring the [Creating infrastructure layer with a Database access] content.
Right click on the Package Explorer, select -> [New] -> [Class] -> [New Java Class] dialog box appears,
Sr. No. Item Input value 1 Package todo.domain.repository.todo
2 Name TodoRepositoryImpl
3 Interfaces todo.domain.repository.todo.TodoRepository
and click [Finish] after entering above information.
Created class stored in the following directory.
Implement the CRUD operations in created class.
Note
Business logic must not be included in RepositoryImpl, it should focus only on inserting and removing (CRUD operation) from the persistence store.
package todo.domain.repository.todo;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Repository;
import todo.domain.model.Todo;
@Repository // (1)
public class TodoRepositoryImpl implements TodoRepository {
private static final Map<String, Todo> TODO_MAP = new ConcurrentHashMap<String, Todo>();
@Override
public Todo findOne(String todoId) {
return TODO_MAP.get(todoId);
}
@Override
public Collection<Todo> findAll() {
return TODO_MAP.values();
}
@Override
public void create(Todo todo) {
TODO_MAP.put(todo.getTodoId(), todo);
}
@Override
public boolean update(Todo todo) {
TODO_MAP.put(todo.getTodoId(), todo);
return true;
}
@Override
public void delete(Todo todo) {
TODO_MAP.remove(todo.getTodoId());
}
@Override
public long countByFinished(boolean finished) {
long count = 0;
for (Todo todo : TODO_MAP.values()) {
if (finished == todo.isFinished()) {
count++;
}
}
return count;
}
}
Sr. No. | Description |
---|---|
(1)
|
To consider Repository as component scan target, add
@Repository annotation at class level. |
Note
In this tutorial, although infrastructure layer belonging classes (RepositoryImpl) are stored under domain layer package (todo.domain
),
if package is divided completed on the basis of layers, it is better to create classes of infrastructure layer under todo.infra
.
However, in a normal project, infrastructure layer rarely changes (such projects are less). Hence, in order to improve the work efficiency, RepositoryImpl can be created in the layer same as the repository of domain layer.
10.1.4.1.4. Service creation¶
First create TodoService
interface.
Right click on the Package Explorer, select -> [New] -> [Interface] -> [New Java Interface] dialog box appears,
Sr. No. Item Input value 1 Package todo.domain.service.todo
2 Name TodoService
and click [Finish] after entering above information.
Created Interface stored in the following directory.
Define method to perform the following business processing in created class.
- Fetch all records of Todo = findAll
- New creation of Todo = create
- Completed of Todo = finish
- Delete of Todo = delete
package todo.domain.service.todo;
import java.util.Collection;
import todo.domain.model.Todo;
public interface TodoService {
Collection<Todo> findAll();
Todo create(Todo todo);
Todo finish(String todoId);
void delete(String todoId);
}
Next, create TodoServiceImpl
class that implements the methods defined in TodoService
interface.
Right click on the Package Explorer, select -> [New] -> [Class] -> [New Java Class] dialog box appears,
Sr. No. Item Input value 1 Package todo.domain.service.todo
2 Name TodoServiceImpl
3 Interfaces todo.domain.service.todo.TodoService
and click [Finish] after entering above information.
Created class stored in the following directory.
package todo.domain.service.todo;
import java.util.Collection;
import java.util.Date;
import java.util.UUID;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.domain.model.Todo;
import todo.domain.repository.todo.TodoRepository;
@Service// (1)
@Transactional // (2)
public class TodoServiceImpl implements TodoService {
private static final long MAX_UNFINISHED_COUNT = 5;
@Inject// (3)
TodoRepository todoRepository;
// (4)
public Todo findOne(String todoId) {
Todo todo = todoRepository.findOne(todoId);
if (todo == null) {
// (5)
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E404] The requested Todo is not found. (id="
+ todoId + ")"));
// (6)
throw new ResourceNotFoundException(messages);
}
return todo;
}
@Override
@Transactional(readOnly = true) // (7)
public Collection<Todo> findAll() {
return todoRepository.findAll();
}
@Override
public Todo create(Todo todo) {
long unfinishedCount = todoRepository.countByFinished(false);
if (unfinishedCount >= MAX_UNFINISHED_COUNT) {
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E001] The count of un-finished Todo must not be over "
+ MAX_UNFINISHED_COUNT + "."));
// (8)
throw new BusinessException(messages);
}
// (9)
String todoId = UUID.randomUUID().toString();
Date createdAt = new Date();
todo.setTodoId(todoId);
todo.setCreatedAt(createdAt);
todo.setFinished(false);
todoRepository.create(todo);
/* REMOVE THIS LINE IF YOU USE JPA
todoRepository.save(todo); // 10
REMOVE THIS LINE IF YOU USE JPA */
return todo;
}
@Override
public Todo finish(String todoId) {
Todo todo = findOne(todoId);
if (todo.isFinished()) {
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E002] The requested Todo is already finished. (id="
+ todoId + ")"));
throw new BusinessException(messages);
}
todo.setFinished(true);
todoRepository.update(todo);
/* REMOVE THIS LINE IF YOU USE JPA
todoRepository.save(todo); // (11)
REMOVE THIS LINE IF YOU USE JPA */
return todo;
}
@Override
public void delete(String todoId) {
Todo todo = findOne(todoId);
todoRepository.delete(todo);
}
}
Sr. No. | Description |
---|---|
(1)
|
To consider Service as component-scan target, add
@Service annotation at class level. |
(2)
|
All public methods will be treated as transaction management by attaching the
@Transactional annotation at class level.By applying annotation, the transaction starts at the timing of method execution starts and transaction commits at the time of method execution successful completion.
However, if unexpected exception occurs in between, the transaction roll-backs.
If database is not used,
@Transactional annotation is not required. |
(3)
|
Inject
TodoRepository implementation using @Inject annotation. |
(4)
|
Logic of fetching single record is used in both delete and finish methods. Hence it should be implemented in a method (OK to make it public by declaring in the interface).
|
(5)
|
Use
org.terasoluna.gfw.common.message.ResultMessage provided in common library, as a class that stores result messages.Currently, for throwing error message,
ResultMessage is added by specifying message type using ResultMessages.error() . |
(6)
|
When targeted data not found,
org.terasoluna.gfw.common.exception.ResourceNotFoundException provided in common library is thrown. |
(7)
|
Regarding the reading process only, put
readOnly=true .By this settings, the optimization of transaction control is done while reading process depending upon the O/R Mapper (Not effective in case of JPA used).
If database is not used,
@Transactional annotation is not required. |
(8)
|
When business error occurs,
org.terasoluna.gfw.common.exception.BusinessException provided in common library is thrown. |
(9)
|
UUID is used to generate a unique value. DB sequence may be used.
|
(10)
|
If database is accessed using Spring Data JPA, instead of
create method, call save method. |
(11)
|
If database is accessed using Spring Data JPA, instead of
update method, call save method. |
Note
In this chapter, error messages are hard coded for simplification, but in reality it is not preferred from maintenance viewpoint. Usually, it is recommended to create message externally in property file. The method for creating the external property file is described in Properties Management.
10.1.4.1.5. Creation of JUnit for Service¶
Todo
TBD
For information about how to Unit test the service, planned to be described in next version later.
10.1.4.2. Creation of application layer¶
Since domain layer implementation is completed, use the domain layer to create application layer.
10.1.4.2.1. Creation of Controller¶
First create Controller that controls screen transition of todo business application.
Right click on the Package Explorer, select -> [New] -> [Class] -> [New Java Class] dialog box appears,
Sr. No. Item Input value 1 Package todo.app.todo
2 Name TodoController
and click [Finish] after entering above information.
Note
It should be noted that the higher level package is different from the domain layer.
Created class stored in the following directory.
package todo.app.todo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller // (1)
@RequestMapping("todo") // (2)
public class TodoController {
}
Sr. No. | Description |
---|---|
(1)
|
In order to make Controller as component-scan target, add
@Controller annotation at class level. |
(2)
|
In order to bring all screen transitions handled by
TodoController , under <contextPath>/todo , set @RequestMapping(“todo”) at class level. |
10.1.4.2.2. Show all TODO implementation¶
Perform below in screen that are created in this tutorial.
- Display new form
- Display all records of TODO
10.1.4.2.2.1. Form creation¶
Create Form class (JavaBean).
Right click on the Package Explorer, select -> [New] -> [Class] -> [New Java Class] dialog box appears,
Sr. No. Item Input value 1 Package todo.app.todo
2 Name TodoForm
3 Interfaces java.io.Serializable
and click [Finish] after entering above information.
Created class stored in the following directory.
Add following property in the created class.
- Title = todoTitle
package todo.app.todo;
import java.io.Serializable;
public class TodoForm implements Serializable {
private static final long serialVersionUID = 1L;
private String todoTitle;
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
}
10.1.4.2.2.2. Implementation of Controller¶
Add list screen display into TodoController
.
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject // (1)
TodoService todoService;
@ModelAttribute // (2)
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list") // (3)
public String list(Model model) {
Collection<Todo> todos = todoService.findAll();
model.addAttribute("todos", todos); // (4)
return "todo/list"; // (5)
}
}
Sr. No. | Description |
---|---|
(1)
|
Add
@Inject annotation for injecting TodoService using DI container.Instance of type
TodoService (instance of TodoServiceImpl ) managed by DI container is injected. |
(2)
|
Initialize Form
By adding
@ModelAttribute annotation, form object of the return value of this method is added to Model with name "todoForm" .It is same as executing
model.addAttribute("todoForm", form) in each method of TodoController . |
(3)
|
set
@RequestMapping annotation such a way that method of list screen display (list method) gets executed when it is requested to /todo/list path.Since
@RequestMapping(“todo”) is being set at class level, only @RequestMapping("list") is fine here. |
(4)
|
Add Todo list to
Model and pass to View. |
(5)
|
If
"todo/list" is returned as View name, WEB-INF/views/todo/list.jsp will be rendered using ViewResolver defined in spring-mvc.xml. |
10.1.4.2.2.3. JSP creation¶
Display Model passed from Controller by creating JSP.
Right click on the Package Explorer, select -> [New] -> [File] -> [New File] dialog box appears,
Sr. No. Item Input value 1 Enter or select the parent folder todo/src/main/webapp/WEB-INF/views/todo
2 File name list.jsp
and click [Finish] after entering above information.
Created file stored in the following directory.
First, implement necessary JSP for following display.
- Input form of TODO
- [Create Todo] button
- List display area of TODO
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
<style type="text/css">
.strike {
text-decoration: line-through;
}
</style>
</head>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<!-- (1) -->
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<!-- (2) -->
<form:input path="todoTitle" />
<form:button>Create Todo</form:button>
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<!-- (3) -->
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}"><!-- (4) -->
<span class="strike">
<!-- (5) -->
${f:h(todo.todoTitle)}
</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
</c:otherwise>
</c:choose></li>
</c:forEach>
</ul>
</div>
</body>
</html>
Sr. No. | Description |
---|---|
(1)
|
Display the form of new creation process.
The
<form:form> tag is used for displaying form.Specify name of the form object added to
Model by Controller in modelAttribute attribute.Specify URL(
<contextPath>/todo/create ) into action attribute for running the new creation process.Since the new creation process is a process of updation, specify the
POST method into the method attribute.<contextPath> to be specified in
action attribute can be fetched by ${pageContext.request.contextPath} . |
(2)
|
Bind form property using
<form:input> tag.Property name of form which is specified in
modelAttribute attribute should match with the value of path attribute. |
(3)
|
Display entire list of Todo using
<c:forEach> tag. |
(4)
|
Determine whether to decorate text using strike through(
text-decoration: line-through; ) to display, if it is completed (finished ). |
(5)
|
To take XSS countermeasures at the time of output of character string, HTML escape should be performed using f:h() function.
Regarding XSS measures, refer to XSS Countermeasures.
|
Right click [todo] project in STS and start Web application by [Run As] -> [Run on Server]. If http://localhost:8080/todo/todo/list is accessed in browser, the following screen gets displayed.
10.1.4.2.3. Create TODO¶
Next, implement a new creation logic after clicking [Create TODO] button on List display screen.
10.1.4.2.3.1. Modifications in Controller¶
Add new creation process into TodoController
.
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
import javax.validation.Valid;
import org.dozer.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject
TodoService todoService;
// (1)
@Inject
Mapper beanMapper;
@ModelAttribute
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list")
public String list(Model model) {
Collection<Todo> todos = todoService.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
@RequestMapping(value = "create", method = RequestMethod.POST) // (2)
public String create(@Valid TodoForm todoForm, BindingResult bindingResult, // (3)
Model model, RedirectAttributes attributes) { // (4)
// (5)
if (bindingResult.hasErrors()) {
return list(model);
}
// (6)
Todo todo = beanMapper.map(todoForm, Todo.class);
try {
todoService.create(todo);
} catch (BusinessException e) {
// (7)
model.addAttribute(e.getResultMessages());
return list(model);
}
// (8)
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Created successfully!")));
return "redirect:/todo/list";
}
}
Sr. No. | Description |
---|---|
(1)
|
At the time of converting form object into domain object. Inject the
Mapper interface of Dozer. |
(2)
|
Set
@RequestMapping annotation such a way that method of new creation process (create method) gets executed when it is requested to /todo/create path using POST method. |
(3)
|
For performing input validation of form, add
@Valid annotation to form argument. Input validation result is stored in the immediate next argument BindingResult . |
(4)
|
Display the list screen by redirecting after successful completion.
Add
RedirectAttributes into argument for storing the information to be redirected. |
(5)
|
Return to list screen in case of input error.
Re-execute
list method as it is necessary to fetch all records of Todo again. |
(6)
|
Create
Todo object from TodoForm object using Mapper interface of Dozer.No need to set if the property name of conversion source and destination is the same.
There is no merit in using
Mapper interface of Dozer to convert only todoTitle property, but it is very convenient in case of multiple properties. |
(7)
|
In case of
BusinessException while executing business logic, add the result message to Model and return to list screen. |
(8)
|
Since it is created successfully, add the result message to flash scope and redirect to list screen.
Since redirect is used, there is no case of browser being read again and a new registration process being
POST . (For details, refer to “About PRG (Post-Redirect-Get) pattern”)Since this time Created successfully message is displayed,
ResultMessages.success() is used. |
10.1.4.2.3.2. Modifications in Form¶
To define input validation rules, add annotation to the form object.
package todo.app.todo;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class TodoForm implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull // (1)
@Size(min = 1, max = 30) // (2)
private String todoTitle;
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
}
Sr. No. | Description |
---|---|
(1)
|
Enable mandatory check using
@NotNull annotation. |
(2)
|
Enable string length check using
@Size annotation. |
10.1.4.2.3.3. Modifications in JSP¶
Add the tag for displaying the result message and input check error.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
<style type="text/css">
.strike {
text-decoration: line-through;
}
</style>
</head>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<!-- (1) -->
<t:messagesPanel />
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<form:input path="todoTitle" />
<form:errors path="todoTitle" /><!-- (2) -->
<form:button>Create Todo</form:button>
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}">
<span style="text-decoration: line-through;">
${f:h(todo.todoTitle)}
</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
</c:otherwise>
</c:choose></li>
</c:forEach>
</ul>
</div>
</body>
</html>
Sr. No. | Description |
---|---|
(1)
|
Display result message using
<t:messagesPanel> tag. |
(2)
|
Display errors in case of input error using
<form:errors> tag. Match the value of path attribute with <form:input> tag. |
If form is submitted by entering appropriate value in the form, success message is displayed as given below.
When 6 or more records are registered and business error occurs, error message is displayed.
If form is submitted by entering null character, the following error message is displayed.
10.1.4.2.3.4. Customize message display¶
If <t:messagesPanel>
is used, following is the HTML output.
<div class="alert alert-success"><ul><li>Created successfully!</li></ul></div>
With the following modifications in style sheet (in <style>
tag of list.jsp
), customize appearance of the result message.
.alert {
border: 1px solid;
}
.alert-error {
background-color: #c60f13;
border-color: #970b0e;
color: white;
}
.alert-success {
background-color: #5da423;
border-color: #457a1a;
color: white;
}
The message is as follows.
Moreover, input error message class can be specified to cssClass
attribute of <form:errors>
tag.
Modify JSP as follows,
<form:errors path="todoTitle" cssClass="text-error" />
and add the following to style sheet.
.text-error {
color: #c60f13;
}
Input error message is as follows.
10.1.4.2.4. Finish TODO¶
Add [Finish] button to List display screen and add completion process of TODO.
10.1.4.2.4.1. Modifications in Form¶
About the completion Form, same TodoForm
is used.
The todoId
property needs to be added to TodoForm
but if simply added, todoId
property check rules for new creation are applied as it is.
For specifying separate rules for new creation and completion in a single Form, set groups
attribute and perform input check rule group.
Add below property in Form class.
- ID → todoId
package todo.app.todo;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class TodoForm implements Serializable {
// (1)
public static interface TodoCreate {
};
public static interface TodoFinish {
};
private static final long serialVersionUID = 1L;
// (2)
@NotNull(groups = { TodoFinish.class })
private String todoId;
// (3)
@NotNull(groups = { TodoCreate.class })
@Size(min = 1, max = 30, groups = { TodoCreate.class })
private String todoTitle;
public String getTodoId() {
return todoId;
}
public void setTodoId(String todoId) {
this.todoId = todoId;
}
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
}
Sr. No. | Description |
---|---|
(1)
|
Create an interface for grouping the input check rules.
For grouping the input check rules, Refer Input Validation.
However as a new creation prcess
TodoCreate interface and as a completion process TodoFinish interface is created. |
(2)
|
todoId is a property for completion process.Therefore in
groups attribute of @NotNull annotation, the TodoFinish interface is specified for indicating input check rule of completion process. |
(3)
|
todoTitle is a property for new creation process.Therefore in
groups attribute of @NotNull annotation and @Size annotation, the TodoCreate interface is specified for indicating input check rule of new creation process. |
10.1.4.2.4.2. Modifications in Controller¶
Add completion process logic to TodoController
.
Take precaution of using @Validated instead of @Valid for executing the group validation.
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
import javax.validation.groups.Default;
import org.dozer.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.app.todo.TodoForm.TodoCreate;
import todo.app.todo.TodoForm.TodoFinish;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject
TodoService todoService;
@Inject
Mapper beanMapper;
@ModelAttribute
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list")
public String list(Model model) {
Collection<Todo> todos = todoService.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
@RequestMapping(value = "create", method = RequestMethod.POST)
public String create(
@Validated({ Default.class, TodoCreate.class }) TodoForm todoForm, // (1)
BindingResult bindingResult, Model model,
RedirectAttributes attributes) {
if (bindingResult.hasErrors()) {
return list(model);
}
Todo todo = beanMapper.map(todoForm, Todo.class);
try {
todoService.create(todo);
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Created successfully!")));
return "redirect:/todo/list";
}
@RequestMapping(value = "finish", method = RequestMethod.POST) // (2)
public String finish(
@Validated({ Default.class, TodoFinish.class }) TodoForm form, // (3)
BindingResult bindingResult, Model model,
RedirectAttributes attributes) {
// (4)
if (bindingResult.hasErrors()) {
return list(model);
}
try {
todoService.finish(form.getTodoId());
} catch (BusinessException e) {
// (5)
model.addAttribute(e.getResultMessages());
return list(model);
}
// (6)
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Finished successfully!")));
return "redirect:/todo/list";
}
}
Sr. No. | Description |
---|---|
(1)
|
Change
@Valid to @Validated for executing group validation.Group of input check rules (group interface) can be specified in
value attribute.Default.class is a group interface provided to apply an input validation rules when group is not specified. |
(2)
|
Set
@RequestMapping annotation such a way that method of completion process (finish method) gets executed when it is requested to /todo/finish path using POST method. |
(3)
|
Specify the group interface (
TodoFinish interface) for Finish processing as group of input check. |
(4)
|
In case of input error, return to list screen.
|
(5)
|
In case of
BusinessException while executing business logic, add the result message to Model and return to list screen. |
(6)
|
Since it is created successfully, add the result message to flash scope and redirect to list screen.
|
Note
Separate Form can also be created for Create and Finish. In case of separate Form class, there is no need to group the input check rules therefore definition of input check rules will be simple.
However, as the number of Form class increase,
- The number of classes increases
- Not possible to centrally manage the input check rules due to duplicate properties increases
Therefore, please note that when the specifications changes, the modification cost will also be more.
Moreover, if multiple Form objects are initialized by @ModelAttribute
method,
unnecessary instance gets generated because every time all Forms are being initialized.
10.1.4.2.4.3. Modifications in JSP¶
Add completion process form.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
</head>
<style type="text/css">
.strike {
text-decoration: line-through;
}
.alert {
border: 1px solid;
}
.alert-error {
background-color: #c60f13;
border-color: #970b0e;
color: white;
}
.alert-success {
background-color: #5da423;
border-color: #457a1a;
color: white;
}
.text-error {
color: #c60f13;
}
</style>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<t:messagesPanel />
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<form:input path="todoTitle" />
<form:errors path="todoTitle" cssClass="text-error" />
<form:button>Create Todo</form:button>
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}">
<span class="strike">${f:h(todo.todoTitle)}</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
<!-- (1) -->
<form:form
action="${pageContext.request.contextPath}/todo/finish"
method="post"
modelAttribute="todoForm"
cssStyle="display: inline-block;">
<!-- (2) -->
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<form:button>Finish</form:button>
</form:form>
</c:otherwise>
</c:choose></li>
</c:forEach>
</ul>
</div>
</body>
</html>
Sr. No. | Description |
---|---|
(1)
|
Display the form for sending the request to complete the TODO if there are incomplete Todo.
Specify URL(
<contextPath>/todo/finish ) into action attribute for running the completion process.Since the completion process is a process of updating, specify the
POST method into the method attribute. |
(2)
|
Pass
todoId as request parameter using <form:hidden> tag.Also while setting the value in
value attribute, HTML escaping should always be performed using f:h() function. |
When pressing the [Finish] button after newly creating Todo, strike-through is shown as below and it can be understood that the operation is completed.
10.1.4.2.5. Delete TODO¶
Add [Delete] button on the list display screen and add the deletion process for TODO removal.
10.1.4.2.5.1. Modification in Form¶
Create TodoForm
for deletion form.
package todo.app.todo;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class TodoForm implements Serializable {
public static interface TodoCreate {
};
public static interface TodoFinish {
};
// (1)
public static interface TodoDelete {
}
private static final long serialVersionUID = 1L;
// (2)
@NotNull(groups = { TodoFinish.class, TodoDelete.class })
private String todoId;
@NotNull(groups = { TodoCreate.class })
@Size(min = 1, max = 30, groups = { TodoCreate.class })
private String todoTitle;
public String getTodoId() {
return todoId;
}
public void setTodoId(String todoId) {
this.todoId = todoId;
}
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
}
Sr. No. | Description |
---|---|
(1)
|
Create the
TodoDelete interface for deletion processing as group of input check rule. |
(2)
|
todoId property is used in deletion process.Therefore, in the
groups attribute of @NotNull annotation of todoId , specify the TodoDelete interface indicating that it is an input validation rules for the deletion process. |
10.1.4.2.5.2. Modifications in Controller¶
Add the logic for delete processing to TodoController
. It is almost same as the completion process.
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
import javax.validation.groups.Default;
import org.dozer.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.app.todo.TodoForm.TodoDelete;
import todo.app.todo.TodoForm.TodoCreate;
import todo.app.todo.TodoForm.TodoFinish;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject
TodoService todoService;
@Inject
Mapper beanMapper;
@ModelAttribute
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list")
public String list(Model model) {
Collection<Todo> todos = todoService.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
@RequestMapping(value = "create", method = RequestMethod.POST)
public String create(
@Validated({ Default.class, TodoCreate.class }) TodoForm todoForm,
BindingResult bindingResult, Model model,
RedirectAttributes attributes) {
if (bindingResult.hasErrors()) {
return list(model);
}
Todo todo = beanMapper.map(todoForm, Todo.class);
try {
todoService.create(todo);
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Created successfully!")));
return "redirect:/todo/list";
}
@RequestMapping(value = "finish", method = RequestMethod.POST)
public String finish(
@Validated({ Default.class, TodoFinish.class }) TodoForm form,
BindingResult bindingResult, Model model,
RedirectAttributes attributes) {
if (bindingResult.hasErrors()) {
return list(model);
}
try {
todoService.finish(form.getTodoId());
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Finished successfully!")));
return "redirect:/todo/list";
}
@RequestMapping(value = "delete", method = RequestMethod.POST) // (1)
public String delete(
@Validated({ Default.class, TodoDelete.class }) TodoForm form,
BindingResult bindingResult, Model model,
RedirectAttributes attributes) {
if (bindingResult.hasErrors()) {
return list(model);
}
try {
todoService.delete(form.getTodoId());
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Deleted successfully!")));
return "redirect:/todo/list";
}
}
Sr. No. | Description |
---|---|
(1)
|
Set @RequestMapping annotation such a way that method of deletion process (delete method) gets executed
when it is requested to /todo/delete path using POST method. |
10.1.4.2.5.3. Modifications in JSP¶
Add deletion process form.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
</head>
<style type="text/css">
.strike {
text-decoration: line-through;
}
.alert {
border: 1px solid;
}
.alert-error {
background-color: #c60f13;
border-color: #970b0e;
color: white;
}
.alert-success {
background-color: #5da423;
border-color: #457a1a;
color: white;
}
.text-error {
color: #c60f13;
}
</style>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<t:messagesPanel />
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<form:input path="todoTitle" />
<form:errors path="todoTitle" cssClass="text-error" />
<form:button>Create Todo</form:button>
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}">
<span class="strike">${f:h(todo.todoTitle)}</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
<form:form
action="${pageContext.request.contextPath}/todo/finish"
method="post"
modelAttribute="todoForm"
cssStyle="display: inline-block;">
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<form:button>Finish</form:button>
</form:form>
</c:otherwise>
</c:choose>
<!-- (1) -->
<form:form
action="${pageContext.request.contextPath}/todo/delete"
method="post" modelAttribute="todoForm"
cssStyle="display: inline-block;">
<!-- (2) -->
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<form:button>Delete</form:button>
</form:form>
</li>
</c:forEach>
</ul>
</div>
</body>
</html>
Sr. No. | Description |
---|---|
(1)
|
Display deletion process form.
Specify URL(
<contextPath>/todo/delete ) into action attribute for running the deletion process.Since the deletion process is a process of updating, specify the
POST method into the method attribute. |
(2)
|
Pass
todoId as request parameter using <form:hidden> tag.Also while setting the value in
value attribute, HTML escaping should always be performed using f:h() function. |
When pressing the [Delete] button in an uncompleted TODO state, TODO is deleted as follows.
10.1.4.2.6. Use of CSS file¶
Although style sheets are directly defined in a JSP file, generally it is defined in the CSS file while developing the actual application.
Here, how to define style sheet into the CSS file are described.
Add the style sheet definitions in the CSS file (src/main/webapp/resources/app/css/styles.css
) that is provided in the blank project.
/* ... */
.strike {
text-decoration: line-through;
}
.alert {
border: 1px solid;
margin-bottom: 5px;
}
.alert-error {
background-color: #c60f13;
border-color: #970b0e;
color: white;
}
.alert-success {
background-color: #5da423;
border-color: #457a1a;
color: white;
}
.text-error {
color: #c60f13;
}
.alert ul {
margin: 15px 0px 15px 0px;
}
#todoList li {
margin-top: 5px;
}
Loading CSS file within JSP.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
<!-- (1) -->
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/app/css/styles.css" type="text/css">
</head>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<t:messagesPanel />
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<form:input path="todoTitle" />
<form:errors path="todoTitle" cssClass="text-error" />
<form:button>Create Todo</form:button>
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}">
<span class="strike">${f:h(todo.todoTitle)}</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
<form:form
action="${pageContext.request.contextPath}/todo/finish"
method="post"
modelAttribute="todoForm"
cssStyle="display: inline-block;">
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<form:button>Finish</form:button>
</form:form>
</c:otherwise>
</c:choose>
<form:form
action="${pageContext.request.contextPath}/todo/delete"
method="post" modelAttribute="todoForm"
cssStyle="display: inline-block;">
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<form:button>Delete</form:button>
</form:form>
</li>
</c:forEach>
</ul>
</div>
</body>
</html>
Sr. No. | Description |
---|---|
(1)
|
Delete the style sheet definitions from the JSP file and load the CSS file in which the style sheets are defined.
|
Following would be the layout if CSS file is applied.
10.1.5. Creating infrastructure layer with a Database access¶
In this section, infrastructure layer for persisting Domain objects in the database is explained.
In this tutorial, it explains how to implement the infrastructure layer using following two O/R Mapper.
- MyBatis3
- Spring Data JPA
10.1.5.1. Creating a blank project dependent on O/R Mapper¶
Here, a blank project dependent on O/R Mapper is created.
At first, recreate the project corresponding to O/R Mapper being used.
Next, copy a file other than **TodoRepositoryImpl class to a newly created projectunder src
folder created upto Creating infrastructure layer with a Database access.
However, file to be copied must be a newly created file or file with added changes. A file without modifications must not be copied.
10.1.5.2. Database set-up¶
Here, perform the Database set-up.
In this tutorial, the H2 Database is used to save the database setup time.
10.1.5.2.1. Modification in todo-infra.properties¶
Modify the src/main/resources/META-INF/spring/todo-infra.properties
settings
for creating tables into H2 Database while booting the AP server.
database=H2
# (1)
database.url=jdbc:h2:mem:todo;DB_CLOSE_DELAY=-1;INIT=create table if not exists todo(todo_id varchar(36) primary key, todo_title varchar(30), finished boolean, created_at timestamp)
database.username=sa
database.password=
database.driverClassName=org.h2.Driver
# connection pool
cp.maxActive=96
cp.maxIdle=16
cp.minIdle=0
cp.maxWait=60000
Sr. No. | Description |
---|---|
(1)
|
Specify the DDL statements to create tables into INIT parameter of the URL connection.
|
Note
If you format the DDL statement that is set to INIT parameter, it will looks like follows.
create table if not exists todo ( todo_id varchar(36) primary key, todo_title varchar(30), finished boolean, created_at timestamp )
10.1.5.3. Creating infrastructure layer with MyBatis3¶
Here, How to create RepositoryImpl of infrastructure layer using MyBatis3 is explained.
If you want to use the Spring Data JPA, you can skip this section and may proceed to the Creating infrastructure layer with Spring Data JPA.
10.1.5.3.1. Create TodoRepository¶
TodoRepository
is created by the same way as it is created for without O/R Mapper.
For creation method, refer [Repository creation].
10.1.5.3.2. Create TodoRepositoryImpl¶
If MyBatis3 is used, RepositoryImpl is automatically generated from the Repository interface (Mapper interface).
Therefore, the creation of TodoRepositoryImpl
is not required. Remove if it is created.
10.1.5.3.3. Create Mapper file¶
Create a Mapper file for defining SQL to be executed when the TodoRepository
interface methods are called.
Right click on the Package Explorer, select -> [New] -> [File] -> [New File] dialog box appears,
Sr. No. Item Input value 1 Enter or select the parent folder todo/src/main/resources/todo/domain/repository/todo
2 File name TodoRepository.xml
and click [Finish] after entering above information.
Created file stored in the following directory.
Describe the SQL to be executed when the TodoRepository
methods defined in the interfaces are called.
<?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">
<!-- (1) -->
<mapper namespace="todo.domain.repository.todo.TodoRepository">
<!-- (2) -->
<resultMap id="todoResultMap" type="Todo">
<id property="todoId" column="todo_id" />
<result property="todoTitle" column="todo_title" />
<result property="finished" column="finished" />
<result property="createdAt" column="created_at" />
</resultMap>
<!-- (3) -->
<select id="findOne" parameterType="String" resultMap="todoResultMap">
<![CDATA[
SELECT
todo_id,
todo_title,
finished,
created_at
FROM
todo
WHERE
todo_id = #{todoId}
]]>
</select>
<!-- (4) -->
<select id="findAll" resultMap="todoResultMap">
<![CDATA[
SELECT
todo_id,
todo_title,
finished,
created_at
FROM
todo
]]>
</select>
<!-- (5) -->
<insert id="create" parameterType="Todo">
<![CDATA[
INSERT INTO todo
(
todo_id,
todo_title,
finished,
created_at
)
VALUES
(
#{todoId},
#{todoTitle},
#{finished},
#{createdAt}
)
]]>
</insert>
<!-- (6) -->
<update id="update" parameterType="Todo">
<![CDATA[
UPDATE todo
SET
todo_title = #{todoTitle},
finished = #{finished},
created_at = #{createdAt}
WHERE
todo_id = #{todoId}
]]>
</update>
<!-- (7) -->
<delete id="delete" parameterType="Todo">
<![CDATA[
DELETE FROM
todo
WHERE
todo_id = #{todoId}
]]>
</delete>
<!-- (8) -->
<select id="countByFinished" parameterType="Boolean"
resultType="Long">
<![CDATA[
SELECT
COUNT(*)
FROM
todo
WHERE
finished = #{finished}
]]>
</select>
</mapper>
Sr. No. | Description |
---|---|
(1)
|
Specify the fully qualified class name of the Repository interfaces (FQCN) in the
namespace attribute of mapper element. |
(2)
|
Define JavaBean mapping with search result (
ResultSet ) in the <resultMap> element.For the mapping file details, Refer Database Access (MyBatis3).
|
(3)
|
Implement the SQL to get one record which matches with the
todoId (PK).Specify the ID of applicable mapping definition in the
resultMap attribute of <select> element. |
(4)
|
Implement the SQL to retrieve all records.
Specify the ID of applicable mapping definition in the
resultMap attribute of <select> element.Although it is not described in the application requirement, records are rearranged as the most recent TODO is displayed at the top.
|
(5)
|
Implement the SQL to insert the Todo object specified in the argument.
Specify the parameter of the class name (FQCN or alias name) in the
parameterType attribute of <insert> element. |
(6)
|
Implement the SQL to update the Todo object specified in the argument.
Specify the parameter of the class name (FQCN or alias name) in the
parameterType attribute of <update> element. |
(7)
|
Implement the SQL to delete the Todo object specified in the argument.
Specify the parameter of the class name (FQCN or alias name) in the
parameterType attribute of <delete> element. |
(8)
|
Implement the SQL to get all records matches with the
finished status specified in the argument. |
Since the creation of infrastructure layer using MyBatis3 has been completed, create the application layer components and services.
Once creation of the Services and application layer are completed, execute the Todo application after starting the AP server, SQL and transaction log output is as following.
date:2016-02-17 13:18:54 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:TRACE logger:o.t.gfw.web.logging.TraceLoggingInterceptor message:[START CONTROLLER] TodoController.list(Model)
date:2016-02-17 13:18:54 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:DEBUG logger:o.s.jdbc.datasource.DataSourceTransactionManager message:Creating new transaction with name [todo.domain.service.todo.TodoServiceImpl.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly; ''
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:DEBUG logger:o.s.jdbc.datasource.DataSourceTransactionManager message:Acquired Connection [net.sf.log4jdbc.ConnectionSpy@4e53de7c] for JDBC transaction
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:DEBUG logger:t.domain.repository.todo.TodoRepository.findAll message:==> Preparing: SELECT todo_id, todo_title, finished, created_at FROM todo
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:DEBUG logger:t.domain.repository.todo.TodoRepository.findAll message:==> Parameters:
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:DEBUG logger:jdbc.sqltiming message: sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
1. SELECT
todo_id,
todo_title,
finished,
created_at
FROM
todo {executed in 0 msec}
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:INFO logger:jdbc.resultsettable message:|--------|-----------|---------|-----------|
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:INFO logger:jdbc.resultsettable message:|TODO_ID |TODO_TITLE |FINISHED |CREATED_AT |
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:INFO logger:jdbc.resultsettable message:|--------|-----------|---------|-----------|
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:INFO logger:jdbc.resultsettable message:|--------|-----------|---------|-----------|
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:DEBUG logger:t.domain.repository.todo.TodoRepository.findAll message:<== Total: 0
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:DEBUG logger:o.s.jdbc.datasource.DataSourceTransactionManager message:Initiating transaction commit
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:DEBUG logger:o.s.jdbc.datasource.DataSourceTransactionManager message:Committing JDBC transaction on Connection [net.sf.log4jdbc.ConnectionSpy@4e53de7c]
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:DEBUG logger:o.s.jdbc.datasource.DataSourceTransactionManager message:Releasing JDBC Connection [net.sf.log4jdbc.ConnectionSpy@4e53de7c] after transaction
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:TRACE logger:o.t.gfw.web.logging.TraceLoggingInterceptor message:[END CONTROLLER ] TodoController.list(Model)-> view=todo/list, model={todoForm=todo.app.todo.TodoForm@2a075f1d, todos=[], org.springframework.validation.BindingResult.todoForm=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
date:2016-02-17 13:18:55 thread:tomcat-http--5 X-Track:390066c43aa94b6588e5bac6a54812b2 level:TRACE logger:o.t.gfw.web.logging.TraceLoggingInterceptor message:[HANDLING TIME ] TodoController.list(Model)-> 756,709,153 ns
10.1.5.4. Creating infrastructure layer with Spring Data JPA¶
Here, How to create RepositoryImpl of infrastructure layer using Spring Data JPA is explained.
10.1.5.4.1. Modification in Entity¶
Configure the JPA annotation for mapping the Todo class with TODO table of Database.
package todo.domain.model;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
// (1)
@Entity
@Table(name = "todo")
public class Todo implements Serializable {
private static final long serialVersionUID = 1L;
// (2)
@Id
@Column(name = "todo_id")
private String todoId;
@Column(name = "todo_title")
private String todoTitle;
@Column(name = "finished")
private boolean finished;
// (3)
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at")
private Date createdAt;
public String getTodoId() {
return todoId;
}
public void setTodoId(String todoId) {
this.todoId = todoId;
}
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
public boolean isFinished() {
return finished;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
}
Sr. No. | Description |
---|---|
(1)
|
Add
@Entity showing that it is JPA entity and set the corresponding table name using @Table . |
(2)
|
Add
@Id to the field corresponding to primary key column. |
(3)
|
It should be clearly specified that
java.util.Date type corresponds to which of java.sql.Date , java.sql.Time , java.sql.Timestamp .Here
Timestamp is specified to createdAt property. |
10.1.5.4.2. Creat TodoRepository¶
Create TodoRepository
using Repository feature of Spring Data JPA.
Right click on the Package Explorer, select -> [New] -> [Interface] -> [New Java Interface] dialog box appears,
Sr. No. Item Input value 1 Package todo.domain.repository.todo
2 Name TodoRepository
3 Extended interfaces org.springframework.data.jpa.repository.JpaRepository<T, ID>
and click [Finish] after entering above information.
package todo.domain.repository.todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import todo.domain.model.Todo;
// (1)
public interface TodoRepository extends JpaRepository<Todo, String> {
@Query("SELECT COUNT(t) FROM Todo t WHERE t.finished = :finished") // (2)
long countByFinished(@Param("finished") boolean finished); // (3)
}
Sr. No. | Description |
---|---|
(1)
|
Specify generics parameter of
JpaRepository .From left to right, Entity Classes (
Todo ), primary key class (String ) is specified.Since basic CRUD operations (
findOne , findAll , save , delete etc.) are defined in JpaRepository interface,only
countByFinished can be defined in TodoRepository . |
(2)
|
Specify
@Query annotation to the JPQL executed at the time of calling countByFinished method |
(3)
|
Set bind variable of JPQL specified in (2) by
@Param annotation.Here,
@Param(“finished”) is added before the method argument finished to embed the value with ”:finished” of JPQL. |
10.1.5.4.3. Create TodoRepositoryImpl¶
If Spring Data JPA is used, RepositoryImpl is automatically generated from the Repository interface.
Therefore, the creation of TodoRepositoryImpl
is not required. Remove if it is created.
Since the creation of infrastructure layer using Spring Data JPA has been completed, create the application layer components and services.
Once creation of the Services and application layer are completed, execute the Todo application after starting the AP server, SQL and transaction log output is as following.
date:2016-02-17 13:32:44 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:TRACE logger:o.t.gfw.web.logging.TraceLoggingInterceptor message:[START CONTROLLER] TodoController.list(Model)
date:2016-02-17 13:32:44 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:DEBUG logger:o.h.e.transaction.spi.AbstractTransactionImpl message:begin
date:2016-02-17 13:32:44 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:DEBUG logger:o.h.e.transaction.internal.jdbc.JdbcTransaction message:initial autocommit status: false
date:2016-02-17 13:32:45 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:DEBUG logger:jdbc.sqltiming message: org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:82)
5. /* select generatedAlias0 from Todo as generatedAlias0 */ select todo0_.todo_id as todo_id1_0_, todo0_.created_at as created_2_0_, todo0_.finished as finished3_0_, todo0_.todo_title as todo_tit4_0_ from todo todo0_ {executed in 1 msec}
date:2016-02-17 13:32:45 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:INFO logger:jdbc.resultsettable message:|--------|-----------|---------|-----------|
date:2016-02-17 13:32:45 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:INFO logger:jdbc.resultsettable message:|TODO_ID |CREATED_AT |FINISHED |TODO_TITLE |
date:2016-02-17 13:32:45 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:INFO logger:jdbc.resultsettable message:|--------|-----------|---------|-----------|
date:2016-02-17 13:32:45 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:INFO logger:jdbc.resultsettable message:|--------|-----------|---------|-----------|
date:2016-02-17 13:32:45 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:DEBUG logger:o.h.e.transaction.spi.AbstractTransactionImpl message:committing
date:2016-02-17 13:32:45 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:DEBUG logger:o.h.e.transaction.internal.jdbc.JdbcTransaction message:committed JDBC Connection
date:2016-02-17 13:32:45 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:TRACE logger:o.t.gfw.web.logging.TraceLoggingInterceptor message:[END CONTROLLER ] TodoController.list(Model)-> view=todo/list, model={todoForm=todo.app.todo.TodoForm@5dac2c75, todos=[], org.springframework.validation.BindingResult.todoForm=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
date:2016-02-17 13:32:45 thread:tomcat-http--5 X-Track:7c34263e0a2143639f3ffd191b35c135 level:TRACE logger:o.t.gfw.web.logging.TraceLoggingInterceptor message:[HANDLING TIME ] TodoController.list(Model)-> 320,129,237 ns
10.1.6. In the end…¶
In this tutorial, following contents have been learnt.
- How to develop basic applications by TERASOLUNA Server Framework for Java (5.x)
- How to build Maven as well as STS (Eclipse) project
- Way of development using application layering of TERASOLUNA Server Framework for Java (5.x).
- Implementation of domain layer with POJO(+ Spring)
- Implementation of application layer with the use of JSP tag libraries and POJO(+ Spring MVC)
- Development of Infrastructure layer with the use of MyBatis3
- Development of Infrastructure layer with the use of Spring Data JPA
- Development of Infrastructure layer without use of O/R Mapper
- The following improvement can be done in the TODO management application.
- As a learning challenge of the application improvement refer the appropriate description of the guidelines.
- To externalize the property (Maximum number of uncompleted TODO) -> Properties Management
- To externalize the messages -> Message Management
- To add pagination function -> Pagination
- To add exception handling -> Exception Handling
- To add double submit protection (Support the transaction token check) -> Double Submit Protection
- To change how to get the system date time -> System Date
10.1.7. Appendix¶
10.1.7.1. Description of the configuration file¶
Description of the configuration files are done for giving an understanding of what type of settings are required to run an application. Settings that are not used in created Todo tutorial application are ignored here.
10.1.7.1.1. web.xml¶
In web.xml
, the settings are done for deploying the Todo application as a Web application.
Following settings are done in created blank project src/main/webapp/WEB-INF/web.xml
.
<?xml version="1.0" encoding="UTF-8"?>
<!-- (1) -->
<web-app 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"
version="3.0">
<!-- (2) -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- Root ApplicationContext -->
<param-value>
classpath*:META-INF/spring/applicationContext.xml
classpath*:META-INF/spring/spring-security.xml
</param-value>
</context-param>
<listener>
<listener-class>org.terasoluna.gfw.web.logging.HttpSessionEventLoggingListener</listener-class>
</listener>
<!-- (3) -->
<filter>
<filter-name>MDCClearFilter</filter-name>
<filter-class>org.terasoluna.gfw.web.logging.mdc.MDCClearFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MDCClearFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>exceptionLoggingFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>exceptionLoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>XTrackMDCPutFilter</filter-name>
<filter-class>org.terasoluna.gfw.web.logging.mdc.XTrackMDCPutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>XTrackMDCPutFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- (4) -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- ApplicationContext for Spring MVC -->
<param-value>classpath*:META-INF/spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- (5) -->
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>false</el-ignored>
<page-encoding>UTF-8</page-encoding>
<scripting-invalid>false</scripting-invalid>
<include-prelude>/WEB-INF/views/common/include.jsp</include-prelude>
</jsp-property-group>
</jsp-config>
<!-- (6) -->
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/views/common/error/systemError.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/views/common/error/resourceNotFoundError.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/WEB-INF/views/common/error/unhandledSystemError.html</location>
</error-page>
<!-- (7) -->
<session-config>
<!-- 30min -->
<session-timeout>30</session-timeout>
<cookie-config>
<http-only>true</http-only>
<!-- <secure>true</secure> -->
</cookie-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
</web-app>
Sr. No. | Description |
---|---|
(1)
|
Declaration to use Servlet3.0.
|
(2)
|
Definition of servlet context listener.
In the blank project,
are configured. |
(3)
|
Definition of servlet filter.
In the blank project,
are configured. |
(4)
|
Definition of DispatcherServlet that is the entry point of Spring MVC.
ApplicationContext used in the DispatcherServlet is the child of ApplicationContext created in step (2).It is also possible to use components loaded in (2) by setting
ApplicationContext created at step (2) as a parent. |
(5)
|
Common definition of JSP.
In the blank project,
are configured. |
(6)
|
Definition of error page.
In the blank project,
related transition destination are defined. |
(7)
|
Definition of session management.
In the blank project,
are defined. |
10.1.7.1.2. Common JSP¶
Tag library settings applied to all JSP are done in Include JSP.
Following settings are done in created blank project src/main/webapp/WEB-INF/views/common/include.jsp
.
<!-- (1) -->
<%@ page session="false"%>
<!-- (2) -->
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!-- (3) -->
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<!-- (4) -->
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>
<!-- (5) -->
<%@ taglib uri="http://terasoluna.org/tags" prefix="t"%>
<%@ taglib uri="http://terasoluna.org/functions" prefix="f"%>
Sr. No. | Description |
---|---|
(1)
|
Definition to avoid session creation while JSP execution.
|
(2)
|
Definition standard tag library.
|
(3)
|
Definition tag library for Spring MVC.
|
(4)
|
Definition tag library for Spring Security.(However, it is not used in this tutorial)
|
(5)
|
Definition EL function and tag library provided in common library.
|
10.1.7.1.3. Bean definition file¶
Following Bean definition files and property files are generated in created blank project.
src/main/resources/META-INF/spring/applicationContext.xml
src/main/resources/META-INF/spring/todo-domain.xml
src/main/resources/META-INF/spring/todo-infra.xml
src/main/resources/META-INF/spring/todo-infra.properties
src/main/resources/META-INF/spring/todo-env.xml
src/main/resources/META-INF/spring/spring-mvc.xml
src/main/resources/META-INF/spring/spring-security.xml
Note
The todo-infra.properties
and todo-env.xml
are not created while creating blank project that does not dependent on the O/R Mapper.
Note
In this guideline, it is recommended to split Bean definition file for each role (layer).
By this way, what is defined in which file can be easily understood and improves the ease of maintenance. It is not effective in case of small application like tutorial but more effective in case of large scale application.
10.1.7.1.3.1. applicationContext.xml¶
Perform entire Todo application related settings in the applicationContext.xml
.
src/main/resources/META-INF/spring/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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- (1) -->
<import resource="classpath:/META-INF/spring/todo-domain.xml" />
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<!-- (2) -->
<context:property-placeholder
location="classpath*:/META-INF/spring/*.properties" />
<!-- (3) -->
<bean id="beanMapper" class="org.dozer.spring.DozerBeanMapperFactoryBean">
<property name="mappingFiles"
value="classpath*:/META-INF/dozer/**/*-mapping.xml" />
</bean>
<!-- Message -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>i18n/application-messages</value>
</list>
</property>
</bean>
<!-- Exception Code Resolver. -->
<bean id="exceptionCodeResolver"
class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver">
<!-- Setting and Customization by project. -->
<property name="exceptionMappings">
<map>
<entry key="ResourceNotFoundException" value="e.xx.fw.5001" />
<entry key="InvalidTransactionTokenException" value="e.xx.fw.7001" />
<entry key="BusinessException" value="e.xx.fw.8001" />
<entry key=".DataAccessException" value="e.xx.fw.9002" />
</map>
</property>
<property name="defaultExceptionCode" value="e.xx.fw.9001" />
</bean>
<!-- Exception Logger. -->
<bean id="exceptionLogger"
class="org.terasoluna.gfw.common.exception.ExceptionLogger">
<property name="exceptionCodeResolver" ref="exceptionCodeResolver" />
</bean>
<!-- Filter. -->
<bean id="exceptionLoggingFilter"
class="org.terasoluna.gfw.web.exception.ExceptionLoggingFilter" >
<property name="exceptionLogger" ref="exceptionLogger" />
</bean>
</beans>
Sr. No. | Description |
---|---|
(1)
|
Import Bean definition file related to domain layer.
|
(2)
|
Loading configuration of the property file.
Load all property files stored under the
src/main/resources/META-INF/spring .Using this setting, it is possible to insert property file value in
${propertyName} format in Bean definition file and to inject using @Value("${propertyName}") in Java class. |
(3)
|
Define Dozer Mapper of Bean conversion library.
For the mapping file, refer to Dozer manual.
|
Tip
By inserting following type of check-mark in [Configure Namespace] tab of the editor, selected XML schema gets enabled and possible to supplement the input using Ctrl+Space at the time of XML editing.
It is recommended to select xsd file without version in [Namespace Versions]. By selecting the xsd file without version, always the latest xsd is used included in the jar therefore no need to concern about the Spring version up.
10.1.7.1.3.2. todo-domain.xml¶
Perform the domain layer related settings of the Todo application in todo-domain.xml
.
src/main/resources/META-INF/spring/todo-domain.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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- (1) -->
<import resource="classpath:META-INF/spring/todo-infra.xml" />
<import resource="classpath*:META-INF/spring/**/*-codelist.xml" />
<!-- (2) -->
<context:component-scan base-package="todo.domain" />
<!-- AOP. -->
<bean id="resultMessagesLoggingInterceptor"
class="org.terasoluna.gfw.common.exception.ResultMessagesLoggingInterceptor">
<property name="exceptionLogger" ref="exceptionLogger" />
</bean>
<aop:config>
<aop:advisor advice-ref="resultMessagesLoggingInterceptor"
pointcut="@within(org.springframework.stereotype.Service)" />
</aop:config>
</beans>
Sr. No. | Description |
---|---|
(1)
|
Import Bean definition file related to infrastructure layer.
|
(2)
|
Components under todo.domain package are target of component scan.
Thus, by attaching annotations like
@Repository , @Service etc to the class under todo.domain package, it get registered as Bean of Spring Framework.It is possible to use registered classes (Bean) by doing DI in Controller and Service class.
|
Note
The <tx:annotation-driven>
tag is set in order to enable the transaction management by @Transactional
annotation
while creating O/R Mapper dependent blank project.
<tx:annotation-driven />
10.1.7.1.3.3. todo-infra.xml¶
Perform infrastructure layer related settings of the TODO application in todo-infra.xml
.
Following settings are done
in created blank project src/main/resources/META-INF/spring/todo-infra.xml
.
Since configuration of todo-infra.xml
is significantly different by the infrastructure layer,
description of each blank project done.
You may skip the explanation other than the created blank project.
10.1.7.1.3.3.1. todo-infra.xml if creating blank project that does not dependent on the O/R Mapper¶
Empty definition file is created as follows while creating blank project that does not dependent on the O/R Mapper.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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
">
</beans>
10.1.7.1.3.3.2. todo-infra.xml of blank project created for MyBatis3¶
The following settings are done in created MyBatis3 project.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
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://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd
">
<!-- (1) -->
<import resource="classpath:/META-INF/spring/todo-env.xml" />
<!-- (2) -->
<!-- define the SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- (3) -->
<property name="dataSource" ref="dataSource" />
<!-- (4) -->
<property name="configLocation" value="classpath:/META-INF/mybatis/mybatis-config.xml" />
</bean>
<!-- (5) -->
<!-- scan for Mappers -->
<mybatis:scan base-package="todo.domain.repository" />
</beans>
Sr. No. | Description |
---|---|
(1)
|
Import the Bean definition file that defines the environment dependent components(Such as DataSource and transaction manager).
|
(2)
|
The bean definition of
SqlSessionFactoryBean as a component for generating a SqlSessionFactory . |
(3)
|
Specify the bean configured DataSource in the
dataSource property.The connection is retrieved from the DataSource specified here by executing the SQL in MyBatis3 process.
|
(4)
|
Specify the path of MyBatis configuration file in the
configLocation property.The specified file loaded while generating a
SqlSessionFactory |
(5)
|
Define
<mybatis:scan> for scanning the Mapper interface,Specify the base package in the
base-package attribute that contains the Mapper interface.Scanned the Mapper interface that stored under the specified package and
automatically generated the thread-safe Mapper object (Proxy object of Mapper interface).
|
Note
mybatis-config.xml
is a configuration file that performs operational setting of MyBatis3.
The following settings are done by default in the blank project.
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- See http://mybatis.github.io/mybatis-3/configuration.html#settings --> <settings> <setting name="mapUnderscoreToCamelCase" value="true" /> <setting name="lazyLoadingEnabled" value="true" /> <setting name="defaultFetchSize" value="100" /> <!-- <setting name="defaultExecutorType" value="REUSE" /> <setting name="jdbcTypeForNull" value="NULL" /> <setting name="localCacheScope" value="STATEMENT" /> --> </settings> <typeAliases> <package name="todo.domain.model" /> <package name="todo.domain.repository" /> <!-- <package name="todo.infra.mybatis.typehandler" /> --> </typeAliases> <typeHandlers> <!-- <package name="todo.infra.mybatis.typehandler" /> --> </typeHandlers> </configuration>
10.1.7.1.3.3.3. todo-infra.xml of blank project created for JPA¶
The following settings are done in created JPA blank project.
<?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:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
">
<!-- (1) -->
<import resource="classpath:/META-INF/spring/todo-env.xml" />
<!-- (2) -->
<jpa:repositories base-package="todo.domain.repository"></jpa:repositories>
<!-- (3) -->
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false" />
<property name="database" value="${database}" />
</bean>
<!-- (4) -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- (5) -->
<property name="packagesToScan" value="todo.domain.model" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
<!-- (6) -->
<property name="jpaPropertyMap">
<util:map>
<entry key="hibernate.hbm2ddl.auto" value="" />
<entry key="hibernate.ejb.naming_strategy"
value="org.hibernate.cfg.ImprovedNamingStrategy" />
<entry key="hibernate.connection.charSet" value="UTF-8" />
<entry key="hibernate.show_sql" value="false" />
<entry key="hibernate.format_sql" value="false" />
<entry key="hibernate.use_sql_comments" value="true" />
<entry key="hibernate.jdbc.batch_size" value="30" />
<entry key="hibernate.jdbc.fetch_size" value="100" />
</util:map>
</property>
</bean>
</beans>
Sr. No. | Description |
---|---|
(1)
|
Import the Bean definition file that defines the environment dependent components(Such as DataSource and transaction manager).
|
(2)
|
Automatically generate the implementation class from Repository interface using the Spring Data JPA.
Specify the package in
base-package attribute of <jpa:repository> tag that contains the Repository. |
(3)
|
Configure JPA implementation vendor.
In order to use Hibernate,
HibernateJpaVendorAdapter is defined as a JPA implementation. |
(4)
|
Define the
EntityManager . |
(5)
|
Specify the package name that treated as a JPA entity classes.
|
(6)
|
Perform the Hibernate related detail settings.
|
10.1.7.1.3.4. todo-infra.properties¶
Perform environment dependent infrastructure layer configuration of Todo application in the todo-infra.properties
.
The todo-infra.properties
is not created while creating blank project that does not dependent on the O/R Mapper.
Following settings are done in created blank project src/main/resources/META-INF/spring/todo-infra.properties
.
# (1)
database=H2
database.url=jdbc:h2:mem:todo;DB_CLOSE_DELAY=-1
database.username=sa
database.password=
database.driverClassName=org.h2.Driver
# (2)
# connection pool
cp.maxActive=96
cp.maxIdle=16
cp.minIdle=0
cp.maxWait=60000
Sr. No. | Description |
---|---|
(1)
|
Database related settings.
In this tutorial, H2 Database is used to save the setup time of the database.
|
(2)
|
Connection pool related settings.
|
Note
The configuration value of this is referred from todo-env.xml
.
10.1.7.1.3.5. todo-env.xml¶
In the todo-env.xml
file, define such components which are differing depending upon the environment.
Following settings are done in created blank project src/main/resources/META-INF/spring/todo-env.xml
.
Here, the file stored in the blank project for MyBatis3 is described as an example.
Furthermore, the todo-env.xml
is not created while creating blank project that does not access the database.
<?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:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
">
<bean id="dateFactory" class="org.terasoluna.gfw.common.date.jodatime.DefaultJodaTimeDateFactory" />
<!-- (1) -->
<bean id="realDataSource" class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${database.driverClassName}" />
<property name="url" value="${database.url}" />
<property name="username" value="${database.username}" />
<property name="password" value="${database.password}" />
<property name="defaultAutoCommit" value="false" />
<property name="maxTotal" value="${cp.maxActive}" />
<property name="maxIdle" value="${cp.maxIdle}" />
<property name="minIdle" value="${cp.minIdle}" />
<property name="maxWaitMillis" value="${cp.maxWait}" />
</bean>
<!-- (2) -->
<bean id="dataSource" class="net.sf.log4jdbc.Log4jdbcProxyDataSource">
<constructor-arg index="0" ref="realDataSource" />
</bean>
<jdbc:initialize-database data-source="dataSource"
ignore-failures="ALL">
<jdbc:script location="classpath:/database/${database}-schema.sql" encoding="UTF-8" />
<jdbc:script location="classpath:/database/${database}-dataload.sql" encoding="UTF-8" />
</jdbc:initialize-database>
<!-- REMOVE THIS LINE IF YOU USE JPA
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
REMOVE THIS LINE IF YOU USE JPA -->
<!-- (3) -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
<property name="rollbackOnCommitFailure" value="true" />
</bean>
</beans>
Sr. No. | Description |
---|---|
(1)
|
Setting of the actual DataSource.
|
(2)
|
Setting of the DataSource.
Specify the DataSource that having JDBC-related logging feature.
By use of
net.sf.log4jdbc.Log4jdbcProxyDataSource , you can output JDBC related log such as SQL log which could be very useful for debugging. |
(3)
|
Setting of the Transaction Manager.
Specify the
transactionManager in id attribute.In case of specifing different name, it is necessary to specify the transaction manager name in
<tx:annotation-driven> tag also.In the blank project, transaction controlling class (
org.springframework.jdbc.datasource.DataSourceTransactionManager ) is configured using JDBC API. |
Note
If blank project created for JPA, Transaction Manager,
transaction controlling class (org.springframework.orm.jpa.JpaTransactionManager
) is configured using JPA API.
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean>
10.1.7.1.3.6. spring-mvc.xml¶
The Spring MVC related definitions are done in spring-mvc.xml
.
src/main/resources/META-INF/spring/spring-mvc.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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!-- (1) -->
<context:property-placeholder
location="classpath*:/META-INF/spring/*.properties" />
<!-- (2) -->
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean
class="org.springframework.data.web.PageableHandlerMethodArgumentResolver" />
<bean
class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
</mvc:argument-resolvers>
<!-- workaround to CVE-2016-5007. -->
<mvc:path-matching path-matcher="pathMatcher" />
</mvc:annotation-driven>
<mvc:default-servlet-handler />
<!-- (3) -->
<context:component-scan base-package="todo.app" />
<!-- (4) -->
<mvc:resources mapping="/resources/**"
location="/resources/,classpath:META-INF/resources/"
cache-period="#{60 * 60}" />
<mvc:interceptors>
<!-- (5) -->
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean class="org.terasoluna.gfw.web.codelist.CodeListInterceptor">
<property name="codeListIdPattern" value="CL_.+" />
</bean>
</mvc:interceptor>
<!-- REMOVE THIS LINE IF YOU USE JPA
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor" />
</mvc:interceptor>
REMOVE THIS LINE IF YOU USE JPA -->
</mvc:interceptors>
<!-- (6) -->
<!-- Settings View Resolver. -->
<mvc:view-resolvers>
<mvc:jsp prefix="/WEB-INF/views/" />
</mvc:view-resolvers>
<bean id="requestDataValueProcessor"
class="org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessor">
<constructor-arg>
<util:list>
<bean
class="org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor" />
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestDataValueProcessor" />
</util:list>
</constructor-arg>
</bean>
<!-- Setting Exception Handling. -->
<!-- Exception Resolver. -->
<bean id="systemExceptionResolver"
class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
<property name="exceptionCodeResolver" ref="exceptionCodeResolver" />
<!-- Setting and Customization by project. -->
<property name="order" value="3" />
<property name="exceptionMappings">
<map>
<entry key="ResourceNotFoundException" value="common/error/resourceNotFoundError" />
<entry key="BusinessException" value="common/error/businessError" />
<entry key="InvalidTransactionTokenException" value="common/error/transactionTokenError" />
<entry key=".DataAccessException" value="common/error/dataAccessError" />
</map>
</property>
<property name="statusCodes">
<map>
<entry key="common/error/resourceNotFoundError" value="404" />
<entry key="common/error/businessError" value="409" />
<entry key="common/error/transactionTokenError" value="409" />
<entry key="common/error/dataAccessError" value="500" />
</map>
</property>
<property name="defaultErrorView" value="common/error/systemError" />
<property name="defaultStatusCode" value="500" />
</bean>
<!-- Setting AOP. -->
<bean id="handlerExceptionResolverLoggingInterceptor"
class="org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor">
<property name="exceptionLogger" ref="exceptionLogger" />
</bean>
<aop:config>
<aop:advisor advice-ref="handlerExceptionResolverLoggingInterceptor"
pointcut="execution(* org.springframework.web.servlet.HandlerExceptionResolver.resolveException(..))" />
</aop:config>
<!-- Setting PathMatcher. -->
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
<property name="trimTokens" value="false" />
</bean>
</beans>
Sr. No. | Description |
---|---|
(1)
|
Configuration of reading property file done
Load all property files stored under the src/main/resources/META-INF/spring.
Using this setting, it is possible to insert property file value in
${propertyName} format in Bean definition file and to inject using @Value("${propertyName}") in Java class. |
(2)
|
Carry out annotation based default settings of Spring MVC.
|
(3)
|
Components under todo.app package that holds classes of application layer are made target of component-scan.
|
(4)
|
Carry out the settings for accessing the static resource (css, images, js etc.).
Set URL path to
mapping attribute and physical path to location attribute.In case of this setting, when there is a request for
<contextPath>/rerources/app/css/styles.css , the WEB-INF/resources/app/css/styles.css is searched. If not found, META-INF/resources/app/css/styles.css is searched in classpath (src/main/resources and jar).If
styles.css is not stored anywhere, 404 error is returned.Here, cache period (3600 seconds = 60 minutes) of static resources is set in
cache-period attribute.cache-period="3600" is also correct, however, in order to demonstrate that it is 60 minutes, it is better to write as cache-period="#{60 * 60}" which uses SpEL. |
(5)
|
Set interceptor that outputs trace log of controller processing.
Set so that it excludes the path under
/resources from mapping. |
(6)
|
Carry out the settings of
ViewResolver .Using these settings, for example, when view name
"hello" is returned from controller, /WEB-INF/views/hello.jsp is executed.Tip
The definition example of using the conventional
|
Note
In blank project created for JPA, as a <mvc:interceptors>
definition,
OpenEntityManagerInViewInterceptor
definition is in enable state.
<mvc:interceptor> <mvc:mapping path="/**" /> <mvc:exclude-mapping path="/resources/**" /> <mvc:exclude-mapping path="/**/*.html" /> <bean class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor" /> </mvc:interceptor>
OpenEntityManagerInViewInterceptor
is a Interceptor that performs start and end of the life cycle of the EntityManager
.
By adding this setting, the application layer (Controller or View class) Lazy Load is supported.
10.1.7.1.3.7. spring-security.xml¶
The Spring Security related definitions are done in spring-security.xml
.
src/main/resources/META-INF/spring/spring-security.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:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
">
<sec:http pattern="/resources/**" security="none"/>
<sec:http>
<sec:form-login/>
<sec:logout/>
<sec:access-denied-handler ref="accessDeniedHandler"/>
<sec:custom-filter ref="userIdMDCPutFilter" after="ANONYMOUS_FILTER"/>
<sec:session-management />
</sec:http>
<sec:authentication-manager />
<!-- CSRF Protection -->
<bean id="accessDeniedHandler"
class="org.springframework.security.web.access.DelegatingAccessDeniedHandler">
<constructor-arg index="0">
<map>
<entry
key="org.springframework.security.web.csrf.InvalidCsrfTokenException">
<bean
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/WEB-INF/views/common/error/invalidCsrfTokenError.jsp" />
</bean>
</entry>
<entry
key="org.springframework.security.web.csrf.MissingCsrfTokenException">
<bean
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/WEB-INF/views/common/error/missingCsrfTokenError.jsp" />
</bean>
</entry>
</map>
</constructor-arg>
<constructor-arg index="1">
<bean
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/WEB-INF/views/common/error/accessDeniedError.jsp" />
</bean>
</constructor-arg>
</bean>
<!-- Put UserID into MDC -->
<bean id="userIdMDCPutFilter" class="org.terasoluna.gfw.security.web.logging.UserIdMDCPutFilter">
</bean>
</beans>
10.1.7.1.4. logback.xml¶
The log output related definitions are done in logback.xml
.
src/main/resources/logback.xml
.<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- (1) -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern><![CDATA[date:%d{yyyy-MM-dd HH:mm:ss}\tthread:%thread\tX-Track:%X{X-Track}\tlevel:%-5level\tlogger:%-48logger{48}\tmessage:%msg%n]]></pattern>
</encoder>
</appender>
<appender name="APPLICATION_LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${app.log.dir:-log}/todo-application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${app.log.dir:-log}/todo-application-%d{yyyyMMdd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern><![CDATA[date:%d{yyyy-MM-dd HH:mm:ss}\tthread:%thread\tX-Track:%X{X-Track}\tlevel:%-5level\tlogger:%-48logger{48}\tmessage:%msg%n]]></pattern>
</encoder>
</appender>
<appender name="MONITORING_LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${app.log.dir:-log}/todo-monitoring.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${app.log.dir:-log}/todo-monitoring-%d{yyyyMMdd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern><![CDATA[date:%d{yyyy-MM-dd HH:mm:ss}\tX-Track:%X{X-Track}\tlevel:%-5level\tmessage:%msg%n]]></pattern>
</encoder>
</appender>
<!-- Application Loggers -->
<!-- (2) -->
<logger name="todo">
<level value="debug" />
</logger>
<!-- TERASOLUNA -->
<logger name="org.terasoluna.gfw">
<level value="info" />
</logger>
<!-- (3) -->
<logger name="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor">
<level value="trace" />
</logger>
<logger name="org.terasoluna.gfw.common.exception.ExceptionLogger">
<level value="info" />
</logger>
<logger name="org.terasoluna.gfw.common.exception.ExceptionLogger.Monitoring" additivity="false">
<level value="error" />
<appender-ref ref="MONITORING_LOG_FILE" />
</logger>
<!-- 3rdparty Loggers -->
<logger name="org.springframework">
<level value="warn" />
</logger>
<logger name="org.springframework.web.servlet">
<level value="info" />
</logger>
<!-- REMOVE THIS LINE IF YOU USE JPA
<logger name="org.hibernate.engine.transaction">
<level value="debug" />
</logger>
REMOVE THIS LINE IF YOU USE JPA -->
<!-- REMOVE THIS LINE IF YOU USE MyBatis3
<logger name="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<level value="debug" />
</logger>
REMOVE THIS LINE IF YOU USE MyBatis3 -->
<logger name="jdbc.sqltiming">
<level value="debug" />
</logger>
<!-- only for development -->
<logger name="jdbc.resultsettable">
<level value="debug" />
</logger>
<root level="warn">
<appender-ref ref="STDOUT" />
<appender-ref ref="APPLICATION_LOG_FILE" />
</root>
</configuration>
Sr. No. | Description |
---|---|
(1)
|
Set appender that outputs the log in standard output.
|
(2)
|
Set so that log of debug level and above is output under todo package.
|
(3)
|
Set log level to ‘trace’ for TraceLoggingInterceptor which is defined in spring-mvc.xml.
|
Note
If you create a blank project that uses the O/R Mapper, the logger to output transaction control-related log has been in enable state.
- Blank project for the JPA
<logger name="org.hibernate.engine.transaction"> <level value="debug" /> </logger>
- Blank project for the MyBatis3
<logger name="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <level value="debug" /> </logger>