0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Java】Eclipse2025でStruts2(ver6系)アプリでデータを挿入する処理について

Last updated at Posted at 2026-01-04

下記のURLにアクセスして、(レイアウトはイマイチですが...)データを挿入する方法について書いていきます。

http://localhost:8080/StrutsApp/userRegister.action

image.png

技術とバージョン

技術 バージョン
Tomcat 9系
Java 21
Spring Framework 5.3.x系
Servlet API(javax) 5.8.x系
spring-security-core 5.8.x系

使用したMavenライブラリ

Spring Security Core

image.png
👇下記をコピペしてください。👇

pom.xml
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>5.8.13</version>
</dependency>

Spring Transaction

image.png

👇下記をコピペしてください。👇

pom.xml
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.3.39</version>
</dependency>

少し整理しながら、「confirmUserInformation.jsp → JavaScript(saveFiles)→ Struts2 Action → MyBatis → 2テーブルINSERT」の王道構成を説明します。
(※ いまのJSPは form submit なので、JavaScriptでPOSTする版 と Struts2フォーム版 の両方を示します)

image.png

① confirmUserInformation.jsp(JavaScriptでPOSTする場合)
JavaScript(FormData を使う)

confirmUserInformation.jsp
<script>
function saveFiles() {
    const formData = new FormData();

    formData.append("userName", "${userName}");
    formData.append("email", "${email}");
    formData.append("password", "${password}");
    formData.append("gender", "${gender eq '男性' ? 'M' : 'F'}");
    formData.append("office", "${office}");

    <s:iterator value="uploadedFileNames">
        formData.append("uploadedFileNames", "<s:property/>");
    </s:iterator>

    fetch("<s:url action='userRegister_saveFiles'/>", {
        method: "POST",
        body: formData
    }).then(res => {
        if (res.ok) {
            location.href = "userRegisterComplete.jsp";
        }
    });
}
</script>

<button onclick="saveFiles()">登録完了</button>

※ 画像自体はすでにサーバに一時保存されている前提(session管理)

② Struts2 Action
UserRegisterAction.java

UserRegisterAction.java
package com.example.StrutsApp.action;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

import org.apache.commons.io.FileUtils;//追加
import org.apache.struts2.ServletActionContext;//追加
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.transaction.annotation.Transactional;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;

import mapper.AdminUserMapper;
import mapper.AdminUserPhotoMapper;
import model.AdminUser;
import model.AdminUserPhoto;

public class UserRegisterAction extends ActionSupport {
	
	private String userName;
	private String email;
	private String password;
	private String gender;
	private String office;
	private Boolean adminrole;
	
	// 可変ファイルアップロード用
	private File[] photos;
	private String[] photosFileName;
	private String[] photosContentType;
	
	@Autowired
	private AdminUserMapper adminUserMapper;
	
	@Autowired
	private AdminUserPhotoMapper adminUserPhotoMapper;
	
	// 永続保存先(サーバ上のフォルダ)
	//private static final String UPLOAD_DIR = "/WEB-INF/upload/path/";
	
	// getter / setter
	public String getUserName() {return userName;}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	
	public String getEmail() {return email;}
	public void setEmail(String email) {
		this.email = email;
	}
	
	public String getPassword() {return password;}
	public void setPassword(String password) {
		this.password = password;
	}
	
	public String getGender() {
		if("M".equals(gender)) return "男性";
	    if("F".equals(gender)) return "女性";
	    return "";
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	
	public String getOffice() {
		return office;
	}
	
	public void setOffice(String office) {
		this.office = office;
	}
	
	public File[] getPhotos() {
		return photos;
	}
	public void setPhotos(File[] photos) {
		this.photos = photos;
	}
	
	public String[] getPhotosFileName() {
		return photosFileName;
	}
	
	public void setPhotosFileName(String[] photosFileName) {
		this.photosFileName = photosFileName;
	}
	
	public String[] getPhotosContentType() {
		return photosContentType;
	}
	
	public void setPhotosContentType(String[] photosContentType) {
		this.photosContentType = photosContentType;
	}
	
	private List<String> uploadedFileNames;
	public List<String> getUploadedFileNames(){return uploadedFileNames;}
	
	@Override
	public String execute() throws IOException{
		uploadedFileNames = new ArrayList<>();
		if(photos !=null && photosFileName !=null) {
			File tmpDir = new File(System.getProperty("java.io.tmpdir"),"Struts2AppTmpFolder");
			tmpDir.mkdir();
			for(int i=0; i<photos.length; i++) {
				File tmpFile = new File(tmpDir,photosFileName[i]);
				FileUtils.copyFile(photos[i], tmpFile);
				uploadedFileNames.add(tmpFile.getAbsolutePath());
			}
		}
		
		// セッションに保存
		Map<String, Object> session = ActionContext.getContext().getSession();
		session.put("uploadedFileNames", uploadedFileNames);
		/*
		// 確認画面用にファイル名コピー
		if(photosFileName != null) {
			uploadedFileNames = new ArrayList<>();
			for(String fileName : photosFileName) {
				uploadedFileNames.add(fileName);
			}
		}
		*/
		return "confirm";
	}
	/*
	private String getUploadDir() {
	    return ServletActionContext.getServletContext().getRealPath("/uploads");
	}
	*/
	
	// 永続保存用メソッド(確認画面から「登録完了」ボタンを押した時)
	@Transactional
	public String saveFiles() throws IOException{
		
		// ===== 1. adminusers INSERT =====
		String adminUserId = UUID.randomUUID().toString();
		
		// パスワードのハッシュ化処理
		String hasedpassword = createHashedPassword(password);
		
		AdminUser user = new AdminUser();
		user.setId(adminUserId);
		user.setName(userName);
		user.setEmail(email);
		user.setPassword(hasedpassword);
		user.setGender(gender);
		user.setOffice(office);
		user.setAdminrole(adminrole);
		
		//adminUserMapper.insert(user);
		
		// ===== 2. ファイルコピー & adminuser_photos INSERT =====
		File uploadDir = new File(getUploadDirFromProperties());
		if(!uploadDir.exists()) {
			uploadDir.mkdir();
		}
		
		int sortOrder = 1;
		for(String tmpPath : uploadedFileNames) {
			File tmpFile = new File(tmpPath);
			File dest = new File(uploadDir, tmpFile.getName());
			FileUtils.copyFile(tmpFile, dest);
			
			AdminUserPhoto photo = new AdminUserPhoto();
			photo.setAdminuserId(adminUserId);
			photo.setFilePath(dest.getAbsolutePath());
			photo.setSortOrder(sortOrder++);
			//adminUserPhotoMapper.insert(photo);
		}
		
		
		/*
		//==================
		// セッションから取得
		Map<String, Object> session = ActionContext.getContext().getSession();
		uploadedFileNames = (List<String>) session.get("uploadedFileNames");
		if(uploadedFileNames == null) {
			addActionError("アップロードファイルが存在しません。");
			return ERROR;
		}
		//⇩本番用
		//File uploadDir = new File(ServletActionContext.getServletContext().getRealPath("/uploads"));
		File uploadDir = new File(getUploadDirFromProperties());
		uploadDir.mkdir();
		for(String tmpPath : uploadedFileNames) {
			File tmpFile = new File(tmpPath);
			File dest = new File(uploadDir, tmpFile.getName());
			FileUtils.copyFile(tmpFile, dest);
		}
		*/
		// セッションから削除
		//session.remove("uploadedFileNames");
		return SUCCESS;
	}
	
	// 保存先パスをプロパティから読み込むメソッド
	private String getUploadDirFromProperties()throws IOException{
		Properties properties = new Properties();
		// クラスパスからsrc/main/resources 配下のプロパティファイルを読み込む
		try(InputStream inputStream = getClass().getClassLoader().getResourceAsStream("filesForDevelopment.properties")){
			if(inputStream == null) {
				return ServletActionContext.getServletContext().getRealPath("/uploads");
			}
			properties.load(inputStream);
		}
		/*
		// プロジェクトのルートに properties がある場合
		FileInputStream fileInputStream = new FileInputStream("filesForDevelopment.properties");
		properties.load(fileInputStream);
		fileInputStream.close();
		*/
		
		String uploadDir = properties.getProperty("uploadedUrl");
		if(uploadDir == null || uploadDir.isEmpty()) {
			// プロパティに値がなければデフォルト
			uploadDir = ServletActionContext.getServletContext().getRealPath("/uploads");
			
		}
		return uploadDir;
	}
	
	// パスワードハッシュ化処理
	private String createHashedPassword(String password) {
		return BCrypt.hashpw(password, BCrypt.gensalt());
	}
	
}

③ MyBatis Mapper(例)
AdminUsersMapper.xml

AdminUsersMapper.xml
<insert id="insert" parameterType="AdminUsers">
    INSERT INTO adminusers
    (id, name, email, password, gender, office, admin_role)
    VALUES
    (#{id}, #{name}, #{email}, #{password}, #{gender}, #{office}, #{adminRole})
</insert>

AdminUserPhotosMapper.xml
<insert id="insert" parameterType="AdminUserPhotos">
    INSERT INTO adminuser_photos
    (adminuser_id, file_path, sort_order)
    VALUES
    (#{adminuserId}, #{filePath}, #{sortOrder})
</insert>

全体のpom.xmlは下記です。

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

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>StrutsApp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>StrutsApp</name>
    <description>Struts 2 Starter</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <struts2.version>6.1.1</struts2.version>
        <log4j2.version>2.19.0</log4j2.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.struts</groupId>
            <artifactId>struts2-core</artifactId>
            <version>${struts2.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.struts</groupId>
            <artifactId>struts2-sitemesh-plugin</artifactId>
            <version>${struts2.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.struts</groupId>
            <artifactId>struts2-spring-plugin</artifactId>
            <version>${struts2.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.struts</groupId>
            <artifactId>struts2-config-browser-plugin</artifactId>
            <version>${struts2.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j2.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j2.version}</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId><!--javax-->
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet.jsp</groupId><!--javax-->
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>org.directwebremoting</groupId>
            <artifactId>dwr</artifactId>
            <version>3.0.2-RELEASE</version>
        </dependency>

        <!-- Tests -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
		<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
		<dependency>
		    <groupId>com.mysql</groupId>
		    <artifactId>mysql-connector-j</artifactId>
		    <version>9.5.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
		<dependency>
		    <groupId>org.mybatis</groupId>
		    <artifactId>mybatis</artifactId>
		    <version>3.5.19</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
		<dependency>
		    <groupId>org.mybatis.generator</groupId>
		    <artifactId>mybatis-generator-core</artifactId>
		    <version>1.4.2</version>
		</dependency>
		<!--<dependency>
			<groupId>com.example</groupId>
			<artifactId>Backend</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>-->
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-core</artifactId>
		    <version>5.8.13</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-tx</artifactId>
		    <version>5.3.39</version>
		</dependency>
    </dependencies>

    <build>
    	<!--<finalName>struts2-archetype-starter</finalName>-->
        <finalName>StrutsApp</finalName>
        <plugins>
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>9.4.46.v20220331</version>
                <configuration>
                    <stopKey>CTRL+C</stopKey>
                    <stopPort>8999</stopPort>
                    <scanIntervalSeconds>10</scanIntervalSeconds>
                    <webAppSourceDirectory>${basedir}/src/main/webapp/</webAppSourceDirectory>
                    <webAppConfig>
                        <contextPath>/StrutsApp</contextPath>
                        <descriptor>${basedir}/src/main/webapp/WEB-INF/web.xml</descriptor>
                    </webAppConfig>
                </configuration>
            </plugin>
            <!--MyBatis追加-->
			<plugin>
				<groupId>org.mybatis.generator</groupId>
				<artifactId>mybatis-generator-maven-plugin</artifactId>
				<version>1.4.1</version>
				<configuration>
					<configurationFile>${project.basedir}/src/main/resources/generatorConfig.xml</configurationFile>
					<overwrite>true</overwrite>
					<includeAllDependencies>true</includeAllDependencies>
				</configuration>
			</plugin>
        </plugins>
    </build>
</project>

④ 重要ポイント(よくハマる所)
✅ 1. 画像は「2回送らない」

アップロード時:一時ディレクトリ

確認画面:ファイルパスのみ保持

登録完了時:本番へコピー+DB登録

✅ 2. トランザクション管理

Spring + MyBatisなら @Transactional を推奨

UserRegisterAction.java
@Transactional
public String saveFiles() { ... }

→ adminusers 失敗時に photos もロールバックされる

✅ 3. パスワードは必ずハッシュ化(超重要)

UserRegisterAction.java
String hashed = BCrypt.hashpw(password, BCrypt.gensalt());
user.setPassword(hashed);

まとめ(結論)

✔ confirm → POST

✔ Actionで adminusers → adminuser_photos の順でINSERT

✔ UUIDで adminuser_id を共有

✔ 画像は「コピー+DB登録」

サイト

Javaでパスワードを安全にハッシュ化する方法〜Apache Commons Codecを使ってみよう〜

【Java】多要素認証実装方法の紹介①

チップ集

BCrypt.hashpw()

JavaでBCrypt.hashpw()を使うには、org.springframework.security.crypto.bcrypt.BCryptクラスを使用し、BCrypt.hashpw(plainPassword, BCrypt.gensalt())のように平文パスワードとランダムなソルトをgensalt()で生成して渡します。これにより、パスワードは「$2a(10)...」のような形式でハッシュ化され、安全に保存・比較できるようになります。 

pom.xml
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>5.x.x</version> <!-- 最新バージョンに合わせる -->
</dependency>

Password.java
import org.springframework.security.crypto.bcrypt.BCrypt;

public class PasswordHasher {
    public static void main(String[] args) {
        String password = "mySecretPassword123";
        
        // 1. ソルトを生成 (デフォルトのコストファクター10)
        String salt = BCrypt.gensalt();
        System.out.println("Salt: " + salt); // 例: $2a$10$nU.P7j.hC5wB5aP.cWp3/u

        // 2. パスワードをハッシュ化
        String hashed = BCrypt.hashpw(password, salt);
        System.out.println("Hashed Password: " + hashed); // 例: $2a$10$nU.P7j.hC5wB5aP.cWp3/u.sVq8m0sZ9tK5n3e4cE5j7sY8dJ7

        // 3. ハッシュ化されたパスワードと平文パスワードの照合
        boolean matches = BCrypt.checkpw(password, hashed);
        System.out.println("Password matches: " + matches); // true

        // 別のパスワードで試す
        boolean notMatches = BCrypt.checkpw("wrongPassword", hashed);
        System.out.println("Wrong password matches: " + notMatches); // false
    }
}

maven

トラブルシューティング

spring-security-coreバージョン7.0.2を入れてアプリ起動すると404エラーになった。

(原因)
Tomcat9とspring-security-coreバージョン違いによるエラーです。

Tomcat 9 を使い続けるなら、spring-security-core 5.8.x に下げる必要があります。

spring-security-core 7.x
Spring Framework 6 / Java 17 / jakarta.servlet前提

spring-security-core 5.8.x
Spring Framework 5 / Java 8〜17 / javax.servlet 対応

今の構成(Tomcat 9 + javax.servlet)では
5.8.x が事実上の最終ラインです。

image.png

(解決方法)
pom.xml の修正例
spring-security-core 5.8.xに変更

pom.xml
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>5.8.13</version>
</dependency>

※ 5.8.x の中で最新安定系(2024年以降もセキュリティ修正あり)

② Spring Framework の衝突を防ぐ(重要)
Struts2 6.1.1 は内部で Spring を使います。
Security が勝手に別バージョンの Spring を引き込まないよう固定します。

pom.xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework-bom</artifactId>
            <version>5.3.39</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

パスワードのハッシュ化用途だけなら(Web セキュリティ機能は不要な場合)、主に使うのは下記のライブラリです。

Sample.java
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

「Transactional を型に解決できません」というエラー

@Transactionalを使用すると下記のエラーが出た。

Transactional を型に解決できません

これは ライブラリ不足ではなく「import 先が無い」ことが原因です。
つまり@Transactionalのクラスがクラスパス上に存在していません。
(解決方法)
今の pom.xml には Spring Transaction モジュールが無いです。

@Transactional spring-tx に含まれています。

なので、pom.xmlに下記を追加しましょう。

pom.xml
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.3.39</version>
</dependency>

その後、下記のインポートしましょう。

Sample.java
import org.springframework.transaction.annotation.Transactional;
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?