end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

(改) spring boot for java + mybatis で sql動的生成

先日、mybatisにおける「select」のsql動的生成を上記entryに記載しましたが、 「insert into」も追加し、以下に記載します。

主なポイントは、以下。

  • UserMstRepository.java における @SelectProvider() + @InsertProvider()
  • UserMstService.java における entityのkey , value の配列変換

その他のポイントは、srcに記載していますので、そちらをご確認下さい。

参考url

class 構成

┌@Controller class ┐
│MyController      │
└─┬───────┘
┌@Service class──┐
│UserMstService    │
└─┬───────┘
┌@Mapper interface ┐
│UserMstRepository │
└─┬───────┘
┌@Entity class ──┐┌複合pkey class  ─┐
│UserMstEntity     ├┤UserMstEntityPkey │
└─┬───────┘└─────────┘
┌mysql ──────┐
│DB TBL user_mst   │
└─────────┘

pom.xml

様々、記載していますが、mybatis-spring-boot-starter により 動的sqlも利用できます。

modelmapper-spring を、entity->dto変換用に記載していますが、 今回は使用しませんでした。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.5</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>jp.end0tknr</groupId>
  <artifactId>MySpring4</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>MySpring4</name>
  <description>seasar2 to spring</description>
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.1.4</version>
    </dependency>
    
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency> <!-- 【JSP & タグ利用】 -->
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>org.modelmapper.extensions</groupId>
      <artifactId>modelmapper-spring</artifactId>
      <version>2.4.2</version>
    </dependency>
    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.9.4</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.5</version>
    </dependency>
  </dependencies>
  
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <excludes>
            <exclude>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
            </exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

application.properties

「mybatis.configuration.map-underscore-to-camel-case=true」により mysqlのsnake caseなcolumn名称が、javaのentityで使用するcamel caseに変換。

# JSP利用の為
spring.mvc.view.prefix= /WEB-INF/view/
spring.mvc.view.suffix= .jsp

# DB接続
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.63.3:3306/bukkenkoutei
spring.datasource.username=xparcadm
spring.datasource.password=ないしょ

# MyBatis
mybatis.mapper-locations:classpath*:/mapper/mysql/*.xml
mybatis.configuration.map-underscore-to-camel-case=true

@Controller と jsp

debug?用に用意しています。

package jp.end0tknr;

import java.sql.Timestamp;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MyController {

    @Autowired
    UserMstService userMstService;

    @RequestMapping(value="/index1")
    public String index1(Model model) {
        System.out.println("START index1");

        List<UserMstEntity> userMstEntities =
                userMstService.selectUserIdList("end0tknr", 1);

        System.out.println(userMstEntities);
        return "index";
    }

    @RequestMapping(value="/index2")
    public String index2(Model model) {
        System.out.println("START index2");

        UserMstEntity userMstEntity =
                userMstService.findById(1,"end0tknr1");

        System.out.println(userMstEntity);

        return "index";
    }

    @RequestMapping(value="/index3")
    public String index3(Model model) {
        System.out.println("START index3");

        for (int i=5; i<=7; i++){
            String newUserId = "end0tknr" + Integer.toString(i);

            // insert into 対象の entity作成
            UserMstEntity newEntity = new UserMstEntity();

            newEntity.setKyotenId(1);
            newEntity.setUserXmileid(newUserId);
            newEntity.setRegXmileid(newUserId);
            newEntity.setRegName(newUserId);
            newEntity.setLastUpdXmileid(newUserId);
            newEntity.setLastUpdName(newUserId);
            newEntity.setUserName(newUserId);

            //java.sql.Timestamp で datetime型用の値を作成
            Timestamp date = new Timestamp(System.currentTimeMillis());
            //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //String dateStr = sdf.format(date);
            newEntity.setRegDate(date);
            newEntity.setLastUpdDate(date);

            newEntity.setIsDelete(0);
            newEntity.setVersion(1);
            newEntity.setUserShozokugroup("jp");
            newEntity.setShokumuId("freelance");
            newEntity.setXmashCode("xmash");

            userMstService.insertByEntity(newEntity);
        }

        return "index";
    }
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>

<a href="/index1">INDEX 1</a><br/>
<a href="/index2">INDEX 2</a><br/>
<a href="/index3">INDEX 3</a> (INSERT INTO)<br/>

</body>
</html>

@Entity

package jp.end0tknr;

import java.io.Serializable;
import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
import javax.persistence.Version;

import lombok.Data;

@Entity
@Table(name = "user_mst")
@IdClass(value=UserMstEntityPkey.class)
@Data
public class UserMstEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /** kyotenIdプロパティ */
    @Id
    @Column(precision = 10, nullable = false, unique = false)
    public Integer kyotenId;

    /** userXmileidプロパティ */
    @Id
    @Column(length = 50, nullable = false, unique = false)
    public String userXmileid;

    /** isDeleteプロパティ */
    @Column(precision = 10, nullable = false, unique = false)
    public Integer isDelete;

    /** versionプロパティ */
    @Version
    @Column(precision = 10, nullable = false, unique = false)
    public Integer version;

    /** userNameプロパティ */
    @Column(length = 400, nullable = false, unique = false)
    public String userName;

    // 以降、省略
}
package jp.end0tknr;

import java.io.Serializable;

import lombok.Data;

@Data
public class UserMstEntityPkey implements Serializable {
    public Integer kyotenId;
    public String userXmileid;
}

DB TBL user_mst

mysql> desc user_mst;
+------------------------------+--------------+------+-----+---------+-------+
| Field                        | Type         | Null | Key | Default | Extra |
+------------------------------+--------------+------+-----+---------+-------+
| kyoten_id                    | int          | NO   | PRI | NULL    |       |
| user_xmileid                 | varchar(50)  | NO   | PRI | NULL    |       |
| user_name                    | varchar(400) | NO   |     | NULL    |       |
| is_delete                    | int          | NO   |     | 0       |       |
| reg_xmileid                  | varchar(50)  | NO   |     | NULL    |       |
| reg_name                     | varchar(400) | NO   |     | NULL    |       |
| reg_date                     | datetime     | NO   |     | NULL    |       |
   <以降、省略>
+------------------------------+--------------+------+-----+---------+-------+

UserMstRepository

package jp.end0tknr;

import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.jdbc.SQL;

@Mapper
public interface UserMstRepository {

    @Select("SELECT * FROM user_mst WHERE kyoten_id=#{kyotenId} and user_xmileid=#{userXmileid}")
    UserMstEntity findById(
            @Param("kyotenId") Integer kyotenId,
            @Param("userXmileid") String userXmileid );

    @SelectProvider(
            type=UserMstRepositorySqlProvider.class,
            method="selectUserIdList" )
    List<UserMstEntity> selectUserIdList(
            @Param("userId") String userId,
            @Param("kyotenId") int kyotenId);

    @InsertProvider(
            type=UserMstRepositorySqlProvider.class,
            method="insertByEntity")
    int insertByEntity(
            @Param("table") String table,
            @Param("atriKeys") String[] atriKeys,
            @Param("atriVals") Object[] atriVals);

    class UserMstRepositorySqlProvider{

        public String insertByEntity(
                String table, String[] atriKeys, Object[] atriVals) {

            SQL sql = new SQL() {{
                INSERT_INTO( table );

                for(int i=0; i<atriKeys.length; i++){
                    String atriKey = atriKeys[i];
                    String atriKeySnake = toSnakeStr( atriKey );
                    VALUES(atriKeySnake, "#{atriVals["+i+"]}");
                }
            }};

            String strSql =sql.toString();
            System.out.println(strSql);

            return strSql;
        }

        public String selectUserIdList(String userId, int kyotenId) {
            SQL sql = new SQL() {{
                SELECT("*");
                FROM("user_mst");
                WHERE("user_xmileid LIKE '${userId}%'");
                WHERE("kyoten_id=${kyotenId}");
               WHERE("is_delete=0");
            }};
            return sql.toString();
        }

        // camel -> snake 変換
        public String toSnakeStr(String camel) {
            String snake =
                    StringUtils.join(
                            StringUtils.splitByCharacterTypeCamelCase(camel), "_")
                    .toLowerCase();
            //数字の前には「_」不要
            snake = snake.replaceAll("(_)([0-9])", "$2");

            return snake;
        }

    }
}

UserMstService

package jp.end0tknr;

import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserMstService {

    @Autowired
    UserMstRepository userMstRepository;

    public UserMstEntity findById(Integer kyotenId, String userXmileid) {
        return userMstRepository.findById(kyotenId, userXmileid);
    }

    public List<UserMstEntity> selectUserIdList(String userXmileid, Integer kyotenId) {
        return userMstRepository.selectUserIdList(userXmileid, kyotenId);
    }

    // entityのkey , value を配列へ変換し、insert
    public int insertByEntity(UserMstEntity newUserEntity) {

        Iterator iterator = null;
        Map describeEntity = null;

        try {
            describeEntity = PropertyUtils.describe(newUserEntity);
            iterator = describeEntity.keySet().iterator();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        String[] atriKeys = new String[describeEntity.size()];
        Object[] atriVals = new Object[describeEntity.size()];

        int i = 0;
        while (iterator.hasNext()) {
            System.out.println(i);
            String atriKey = (String)iterator.next();
            System.out.println(atriKey);
            Object atriVal = describeEntity.get(atriKey);
            System.out.println(atriVal);
            atriKeys[i] = atriKey;
            atriVals[i] = atriVal;
            i++;
        }

        return userMstRepository.insertByEntity("user_mst", atriKeys, atriVals);
    }

}