Spring Bootで独自の認証を組み込む
認証を独自に実装したい
DBの認証やアルゴリズムがちょっと特殊な場合など
今回やったこと
・AuthenticationProviderを実装したAuthenticationProviderImplクラスを作成してここで認証を実施
・WebSecurityConfigurerAdapterにAuthenticationProviderImplを設定する
独自認証実装
AuthenticationProviderImpl.java
package com.example.config; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; import com.example.entity.User; @Component public class AuthenticationProviderImpl implements AuthenticationProvider { private static final Logger logger = LoggerFactory.getLogger(AuthenticationProviderImpl.class); @Override public Authentication authenticate(Authentication auth) throws AuthenticationException { // ユーザーとパスワードを取得 String id = auth.getName(); String password = auth.getCredentials().toString(); // 未設定の場合はエラー if ("".equals(id) || "".equals(password)) { // 例外はSpringSecurityにあったものを適当に使用 throw new AuthenticationCredentialsNotFoundException("ログイン情報に不備があります。"); } // 認証情報を取得 User user = authCheck(id, password); if (user == null) { throw new AuthenticationCredentialsNotFoundException("ログイン情報が存在しません。"); } // トークンを返却 return new UsernamePasswordAuthenticationToken(user, password, auth.getAuthorities()); } @Override public boolean supports(Class<?> token) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(token); } @SuppressWarnings({ "unchecked", "rawtypes" }) private User authCheck(String username, String password) { User user = null; List<Map<String, String>> list = new ArrayList<>(); Map map; map = new HashMap<String, String>(){ private static final long serialVersionUID = 1L; {put("username", "admin");} {put("password", "password");} {put("role", "ADMIN");} }; list.add(map); map = new HashMap<String, String>(){ private static final long serialVersionUID = 1L; {put("username", "user");} {put("password", "password");} {put("role", "USER");} }; list.add(map); map = new HashMap<String, String>(){ private static final long serialVersionUID = 1L; {put("username", "hogehoge");} {put("password", "password2");} {put("role", "USER");} }; list.add(map); for(Map map1 : list) { if (map1.get("username").equals(username) && map1.get("password").equals(password)){ user = new User(); user.setUsername(map1.get("username").toString()); user.setPassword(map1.get("password").toString()); user.setRole(map1.get("role").toString()); break; } } return user; } }
※独自実装対応
セキュリティ設定
WebSecurityConfig.java
package com.example.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import com.example.handler.SampleAuthenticationFailureHandler; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // 認可の設定 httpSecurity .authorizeRequests() .antMatchers("/css/**", "/img/**", "/js/**", "/login").permitAll() // /loginは認証なしでアクセス可能 .antMatchers("/admin/**").hasRole("ADMIN") // ADMINユーザーのみアクセス可能 .antMatchers("/user/**").hasRole("USER") // USERユーザーのみアクセス可能 .anyRequest() .authenticated(); // ログイン設定 httpSecurity .formLogin() .loginProcessingUrl("/login") // 認証処理用 .loginPage("/login") // ログインページ .failureHandler(new SampleAuthenticationFailureHandler()) // 認証失敗時のハンドラクラス .usernameParameter("username") // ユーザー名のパラメータ .passwordParameter("password") // パスワードのパラメータ .defaultSuccessUrl("/") // 認証成功時の遷移先 .and(); // ログアウト設定 httpSecurity .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout**")) // ログアウト処理のパス .logoutSuccessUrl("/login"); // ログイン完了後のパス } @Configuration protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter { @Autowired private AuthenticationProviderImpl authenticationProvider; @Override public void init(AuthenticationManagerBuilder auth) throws Exception { // 認証方法を設定する auth.authenticationProvider(authenticationProvider); } } }
※authenticationProviderで認証設定を行う
エンティティ
User.java
package com.example.entity; import java.util.ArrayList; import java.util.Collection; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import org.springframework.data.annotation.Transient; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @Entity @Table(name = "user") public class User implements UserDetails { /** * */ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name="username") private String username; @Column(name="password") private String password; @Column(name="role") private String role; public void setUsername(String username){ this.username = username; } public void setPassword(String password){ this.password = password; } public String getRole() { return this.role; } public void setRole(String role){ this.role = role; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { // TODO Auto-generated method stub Collection<GrantedAuthority> authorityList = new ArrayList<>(); authorityList.add(new SimpleGrantedAuthority("ROLE_" + this.role)); return authorityList; } @Override public String getPassword() { // TODO Auto-generated method stub return this.password; } @Override public String getUsername() { // TODO Auto-generated method stub return this.username; } @Override public boolean isAccountNonExpired() { // TODO Auto-generated method stub return true; } @Override public boolean isAccountNonLocked() { // TODO Auto-generated method stub return true; } @Override public boolean isCredentialsNonExpired() { // TODO Auto-generated method stub return true; } @Override public boolean isEnabled() { // TODO Auto-generated method stub return true; } }
※user情報設定用にsetterを付与
所感
これでなんとかDB以外のアクセスも対応できそう。
つかうかどうかはまだ不明ですがね…
Spring Bootで独自のエラー画面を表示したい
エラー画面がほしい
システムの画面はちょっと困る
やること
・EmbeddedServletContainerCustomizerを実装する
・ErrorControllerクラスを作成する
・Viewを作成する
EmbeddedServletContainerCustomizer実装
Customizer.java
package com.example.error; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.web.servlet.ErrorPage; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; @Component public class Customizer implements EmbeddedServletContainerCustomizer { @Override public void customize(ConfigurableEmbeddedServletContainer container) { container.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN, "/403")); container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404")); } }
※@Componentを付与して対応
エラー用
ErrorController.java
package com.example.controller.error; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.http.HttpStatus; @Controller public class ErrorController { @RequestMapping(value = "/403", method = RequestMethod.GET) @ResponseStatus(HttpStatus.NOT_FOUND) public String forbidden() { return "errorpage/403"; } @RequestMapping(value = "/404", method = RequestMethod.GET) @ResponseStatus(HttpStatus.NOT_FOUND) public String notFound() { return "errorpage/404"; } }
※エラーページを指定する
エラー画面
errorpage/403.html
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-type" content="text/html; charset=UTF-8" /> </head> <body> 403 Forbidden </body> </html>
errorpage/404.html
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-type" content="text/html; charset=UTF-8" /> </head> <body> 404 Not Found </body> </html>
単純にtemplates配下にerrorディレクトリ作成するだけでも対応できるかも…
画面
Spring Bootでセキュリティに権限を設定する
認可
ユーザーに特定の条件を付与してアクセス権限みたいなものを設定する
今回は1つの権限で1つのURLへのアクセスを制御する。
実際はロールとかいうもので1ユーザーが複数持てるように管理すべきだが面倒なんで…
やること
ユーザーテーブルに権限のエリアを追加
WebSecurityConfigurerAdapterの派生クラスを修正
ユーザーのエンティティクラスで権限を設定するように対応
テーブル
DROP TABLE if EXISTS user; create table if not exists user ( id int not null auto_increment, username varchar(255) not null, password varchar(255) not null, role varchar(255) not null, primary key (id) );
※roleを追加
セキュリティ設定
WebSecurityConfig.java
package com.example.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import com.example.handler.SampleAuthenticationFailureHandler; import com.example.service.UserServiceImpl; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // 認可の設定 httpSecurity .authorizeRequests() .antMatchers("/css/**", "/img/**", "/js/**", "/login").permitAll() // /loginは認証なしでアクセス可能 .antMatchers("/admin/**").hasRole("ADMIN") // ADMINユーザーのみアクセス可能 .antMatchers("/user/**").hasRole("USER") // USERユーザーのみアクセス可能 .anyRequest() .authenticated(); // ログイン設定 httpSecurity .formLogin() .loginProcessingUrl("/login") // 認証処理用 .loginPage("/login") // ログインページ .failureHandler(new SampleAuthenticationFailureHandler()) // 認証失敗時のハンドラクラス .usernameParameter("username") // ユーザー名のパラメータ .passwordParameter("password") // パスワードのパラメータ .defaultSuccessUrl("/") // 認証成功時の遷移先 .and(); // ログアウト設定 httpSecurity .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout**")) // ログアウト処理のパス .logoutSuccessUrl("/login"); // ログイン完了後のパス } @Configuration protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter { @Autowired UserServiceImpl userDetailsService; @Override public void init(AuthenticationManagerBuilder auth) throws Exception { // 認証するユーザーを設定する auth.userDetailsService(userDetailsService); } } }
エンティティ
User.java
package com.example.entity; import java.util.ArrayList; import java.util.Collection; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import org.springframework.data.annotation.Transient; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @Entity @Table(name = "user") public class User implements UserDetails { /** * */ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name="username") private String username; @Column(name="password") private String password; @Column(name="role") private String role; public String getRole() { return this.role; } public void setRole(String role){ this.role = role; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { // TODO Auto-generated method stub Collection<GrantedAuthority> authorityList = new ArrayList<>(); authorityList.add(new SimpleGrantedAuthority("ROLE_" + this.role)); return authorityList; } @Override public String getPassword() { // TODO Auto-generated method stub return this.password; } @Override public String getUsername() { // TODO Auto-generated method stub return this.username; } @Override public boolean isAccountNonExpired() { // TODO Auto-generated method stub return true; } @Override public boolean isAccountNonLocked() { // TODO Auto-generated method stub return true; } @Override public boolean isCredentialsNonExpired() { // TODO Auto-generated method stub return true; } @Override public boolean isEnabled() { // TODO Auto-generated method stub return true; } }
※今回は権限テーブルで複数持たないのでUserDetailsを実装したgetAuthoritiesメソッドでROLEを生成して設定するように対応
※ROLE_XXXのような形で設定しておく必要があります。
動作
Spring Bootで認証を試す
なんか結構難しいけど
いろいろな記事見ててモヤモヤしてて他の事が手につかなくなったのでちょっとだけ試す。
認証機能
ログインやログアウトを行う機能
プロジェクト作成時の設定
pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
データベース設定
create.sql
DROP TABLE if EXISTS user; create table if not exists user ( id int not null auto_increment, username varchar(255) not null, password varchar(255) not null, primary key (id) );
data.sql
insert into user (id, username, password) values (1, 'user', 'password');
アプリケーション設定
application.properties
# server server.port=8081 # database datasource spring.datasource.url=jdbc:mysql://192.168.33.10/myapp?useSSL=false spring.datasource.username=app spring.datasource.password=Password123@ spring.datasource.driver-class-name=com.mysql.jdbc.Driver # database jpa spring.jpa.database=MYSQL spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect # default user #security.user.name=admin #security.user.password=admin
エンティティを作成
User.java
package com.example.entity; import java.util.Collection; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @Entity @Table(name = "user") public class User implements UserDetails { /** * */ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name="username") private String username; @Column(name="password") private String password; @Override public Collection<? extends GrantedAuthority> getAuthorities() { // TODO Auto-generated method stub return null; } @Override public String getPassword() { // TODO Auto-generated method stub return this.password; } @Override public String getUsername() { // TODO Auto-generated method stub return this.username; } @Override public boolean isAccountNonExpired() { // TODO Auto-generated method stub return true; } @Override public boolean isAccountNonLocked() { // TODO Auto-generated method stub return true; } @Override public boolean isCredentialsNonExpired() { // TODO Auto-generated method stub return true; } @Override public boolean isEnabled() { // TODO Auto-generated method stub return true; } }
※認証用にUserDetailsを実装する必要があるらしい
リポジトリ
UserRepository.java
package com.example.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.example.entity.User; @Repository public interface UserRepository extends JpaRepository<User, Long>{ public User findByUsername(String username); }
※ユーザー名から取得用のメソッドを準備
サービス
UserServiceImpl.java
package com.example.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import com.example.entity.User; import com.example.repository.UserRepository; @Component public class UserServiceImpl implements UserDetailsService{ @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (StringUtils.isEmpty(username)) { throw new UsernameNotFoundException(""); } User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException(""); } return user; } }
※UserDetailsServiceを実装してユーザー情報取得用の実装を行う
認証設定
WebSecurityConfig.java
package com.example.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import com.example.handler.SampleAuthenticationFailureHandler; import com.example.service.UserServiceImpl; @Configuration @EnableWebMvcSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // 認可の設定 httpSecurity .authorizeRequests() .antMatchers("/login").permitAll() // /loginは認証なしでアクセス可能 .anyRequest() .authenticated(); // ログイン設定 httpSecurity .formLogin() .loginProcessingUrl("/login") // 認証処理用 .loginPage("/login") // ログインページ .failureHandler(new SampleAuthenticationFailureHandler()) // 認証失敗時のハンドラクラス .usernameParameter("username") // ユーザー名のパラメータ .passwordParameter("password") // パスワードのパラメータ .defaultSuccessUrl("/") // 認証成功時の遷移先 .and(); // ログアウト設定 httpSecurity .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout**")) // ログアウト処理のパス .logoutSuccessUrl("/login"); // ログイン完了後のパス } @Configuration protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter { @Autowired UserServiceImpl userDetailsService; @Override public void init(AuthenticationManagerBuilder auth) throws Exception { // 認証するユーザーを設定する auth.userDetailsService(userDetailsService); } } }
SampleAuthenticationFailureHandler.java
package com.example.handler; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; public class SampleAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException) throws IOException, ServletException { String errorId = ""; // ExceptionからエラーIDをセットする if(authenticationException instanceof BadCredentialsException){ errorId = "ERROO1"; } // ログイン画面にリダイレクトする httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/login" + "?error=" + errorId); } }
画面
TopController.java
package com.example.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class TopController { @RequestMapping(value = "/", method = {RequestMethod.GET} ) public String index(){ return "index"; } }
LoginController.java
package com.example.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class LoginController { @RequestMapping(value = "/login") public String index() { return "login"; } }
SampleController.java
package com.example.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class SampleController { @RequestMapping(value = "/sample") public String index() { return "sample"; } }
index.html
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-type" content="text/html; charset=UTF-8" /> </head> <body> こんにちは! <form action="#" th:action="@{/logout}" method="POST"> <input type="submit" value="ログアウト" /> </form> </body> </html>
login.html
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>ログイン</title> <meta http-equiv="Content-type" content="text/html; charset=UTF-8" /> </head> <body> <form action="" th:action="@{/login}" method="post"> <p> ユーザーID: <input type="text" name="username" /> </p> <p> パスワード: <input type="password" name="password" /> </p> <p> <input type="submit" value="ログイン" /> </p> </form> <div th:if="${session['SPRING_SECURITY_LAST_EXCEPTION']} != null"> <span th:text="${session['SPRING_SECURITY_LAST_EXCEPTION'].message}"></span> </div> </body> </html>
sample.html
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-type" content="text/html; charset=UTF-8" /> </head> <body> こんにちは!サンプルページです <form action="#" th:action="@{/logout}" method="POST"> <input type="submit" value="ログアウト" /> </form> </body> </html>
動作サンプル
所感
認証ってセキュリティの部分なので結構深く作っているようでいろいろ混乱中です。
いろいろな人の記事みてもなんかいまいちわからないので動かしてみることにした
最初は勝手にbasic認証になっていたりと「いや、まだ使わないんですけど…」とかいう部分もあるので戸惑いながらもやってみて
なんとか認証自体は動いている状態までこぎつけた
今回はパスワードが平文のままだったりサンプルのコピペ部分もいくつかあるのでもう少しいろいろ試していかないと…
doma2のsql文調査
SQL文の複合条件とか気になるので調べる
SELECT、INSERT、UPDATEらへんの挙動
SELECT
基本的にはSQLを作らなければならない
基本形
@Select List<Item> findAll();
※@Selectアノテーションを付けたものが対象となります。
↓
対応するSQL
select /*%expand*/* from item order by id asc
条件あり
@Select Item findCondOne(Integer id);
↓
select /*%expand*/* from item where id = /* id */1
可変条件
@Select List<Item> findCondAll(Integer id, String name, Integer price);
↓
select /*%expand*/* from item where /*%if id != null || name != null || price != null */ /*%if id != null */ id = /* id */1 /*%end*/ /*%if name != null */ and name = /* name */'a' /*%end*/ /*%if price != null */ and price = /* price */1 /*%end*/ /*%end*/ order by id asc
※複数の条件を任意に条件に設定する場合はちょっと工夫が必要
INSERT
SQLを必要とする場合と必要ない場合がある。
SQLを作成する
@Insert(sqlFile = true) int insertExecute(Item entity);
※アノテーションにsqlFile = trueを付与
↓
insert into item (name, price, create_at, update_at) values (/* entity.getName() */'a', /* entity.getPrice() */100, /* entity.getCreateAt() */'2016-12-20T11:22:33', /* entity.getUpdateAt() */'2016-12-20T11:22:33')
※バインド変数を設定する
UPDATE
SQLを必要とする場合と必要ない場合がある。
SQLを作成する
@Update(sqlFile = true) int updateExecute(Item entity, String update);
↓
update item set name = /* entity.getName() */'a', price = /* entity.getPrice() */1, update_at = /* entity.getUpdateAt() */'2016-12-24 02:56:55.33' where id = /* entity.getId() */1 and update_at = /* update */'2016-12-24 02:56:55.33'
※バインド変数を設定する
今回はここまで
doma2によるデータの抽出を試す
この前は自動生成しかしていないので
自動生成なしで準備して見る
ライブラリ
doma2
mysql
プロジェクト構成
. ├── bin │ ├── META-INF │ │ └── com │ │ └── example │ │ └── dao │ │ └── ItemDao │ │ └── findAll.sql │ ├── com │ │ └── example │ │ ├── Sample01$1.class │ │ ├── Sample01.class │ │ ├── config │ │ │ └── AppConfig.class │ │ ├── dao │ │ │ ├── ItemDao.class │ │ │ └── ItemDaoImpl.class │ │ └── entity │ │ ├── Item.class │ │ └── _Item.class │ ├── doma-2.0.1.jar │ └── mysql-connector-java-5.1.40.jar ├── lib │ ├── doma-2.0.1.jar │ └── mysql-connector-java-5.1.40.jar └── src ├── META-INF │ └── com │ └── example │ └── dao │ └── ItemDao │ └── findAll.sql └── com └── example ├── Sample01.java ├── config │ └── AppConfig.java ├── dao │ └── ItemDao.java └── entity └── Item.java
設定情報クラス
AppConfig
package com.example.config; import javax.sql.DataSource; import org.seasar.doma.SingletonConfig; import org.seasar.doma.jdbc.Config; import org.seasar.doma.jdbc.dialect.Dialect; import org.seasar.doma.jdbc.dialect.MysqlDialect; import org.seasar.doma.jdbc.tx.LocalTransactionDataSource; import org.seasar.doma.jdbc.tx.LocalTransactionManager; import org.seasar.doma.jdbc.tx.TransactionManager; @SingletonConfig public class AppConfig implements Config { private static final AppConfig INSTANCE = new AppConfig(); private final Dialect dialect; private final LocalTransactionDataSource dataSource; private final TransactionManager transactionManager; private AppConfig() { dialect = new MysqlDialect(); dataSource = new LocalTransactionDataSource("jdbc:mysql://192.168.33.10:3306/myapp?useSSL=false", "app", "Password123@"); transactionManager = new LocalTransactionManager(dataSource.getLocalTransaction(getJdbcLogger())); } @Override public DataSource getDataSource() { // TODO Auto-generated method stub return dataSource; } @Override public Dialect getDialect() { // TODO Auto-generated method stub return dialect; } @Override public TransactionManager getTransactionManager() { return transactionManager; } public static AppConfig singleton() { return INSTANCE; } }
エンティティ
Item
package com.example.entity; import java.time.LocalDateTime; import org.seasar.doma.Column; import org.seasar.doma.Entity; import org.seasar.doma.Id; import org.seasar.doma.Table; @Entity @Table(name = "item") public class Item { @Id private Integer id; @Column(name = "name") private String name; @Column(name = "price") private Integer price; @Column(name = "create_at") private LocalDateTime createAt; @Column(name = "update_at") private LocalDateTime updateAt; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } public LocalDateTime getCreateAt() { return createAt; } public void setCreateAt(LocalDateTime createAt) { this.createAt = createAt; } public LocalDateTime getUpdateAt() { return updateAt; } public void setUpdateAt(LocalDateTime updateAt) { this.updateAt = updateAt; } }
Dao
ItemDao
package com.example.dao; import java.util.List; import org.seasar.doma.Dao; import org.seasar.doma.Select; import com.example.config.AppConfig; import com.example.entity.Item; @Dao(config = AppConfig.class) public interface ItemDao { @Select List<Item> findAll(); }
findAll.sql
select /*%expand*/* from item order by id asc
実行用クラス
Sample01.java
package com.example; import java.util.List; import org.seasar.doma.jdbc.tx.TransactionManager; import com.example.config.AppConfig; import com.example.dao.ItemDao; import com.example.dao.ItemDaoImpl; import com.example.entity.Item; public class Sample01 { public static void main(String[] args) { TransactionManager tm = AppConfig.singleton().getTransactionManager(); tm.required(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub ItemDao dao = new ItemDaoImpl(); List<Item> list = dao.findAll(); System.out.println(list.size()); } }); } }
↓
実行結果
12 24, 2016 1:40:25 午前 org.seasar.doma.jdbc.tx.LocalTransaction begin 情報: [DOMA2063] ローカルトランザクション[1190654826]を開始しました。 12 24, 2016 1:40:25 午前 com.example.dao.ItemDaoImpl findAll 情報: [DOMA2220] ENTER : クラス=[com.example.dao.ItemDaoImpl], メソッド=[findAll] 12 24, 2016 1:40:25 午前 com.example.dao.ItemDaoImpl findAll 情報: [DOMA2076] SQLログ : SQLファイル=[META-INF/com/example/dao/ItemDao/findAll.sql], select id, name, price, create_at, update_at from item order by id asc 12 24, 2016 1:40:25 午前 com.example.dao.ItemDaoImpl findAll 情報: [DOMA2221] EXIT : クラス=[com.example.dao.ItemDaoImpl], メソッド=[findAll] 6 12 24, 2016 1:40:25 午前 org.seasar.doma.jdbc.tx.LocalTransaction commit 情報: [DOMA2067] ローカルトランザクション[1190654826]をコミットしました。 12 24, 2016 1:40:25 午前 org.seasar.doma.jdbc.tx.LocalTransaction commit 情報: [DOMA2064] ローカルトランザクション[1190654826]を終了しました。
動きました(^^)
ログにsqlを表示する?
参考
Doma2のgenで自動生成を行う
Doma2
ORマッパーらしい
SQLファイルとか書いてなんかできるらしいよ
今回やること
Doma2でDBを参照していい感じのエンティティとかを作成できるらしいので準備して見る。
ライブラリ
doma2
doma2-gen
freemarker
mysl
設定
プロジェクトを作成する
ライブラリを指定の場所へ配置しておく
設定ファイル編集
doma-gen-build.xml
<?xml version="1.0" encoding="UTF-8"?> <project name="doma-gen-example" default="gen" basedir="."> <!-- javaDestDir --> <property name="javaDestDir" value="src"/> <!-- sqlDestDir --> <property name="sqlDestDir" value="src"/> <!-- dialectName --> <property name="dialectName" value="mysql"/> <!-- driverClassName --> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.33.10:3306/myapp"/> <property name="user" value="app"/> <property name="password" value="Password123@"/> <!-- package --> <property name="entityPackageName" value="com.example.mshige1979.server.db.entity"/> <property name="daoPackageName" value="com.example.mshige1979.server.db.dao"/> <property name="configClassName" value="com.example.mshige1979.server.db.config.AppConfig"/> <property name="sqlTestClassName" value="com.example.mshige1979.server.db.SqlTest"/> <property name="sqlFilePattern" value="META-INF/**/*.sql"/> <path id="classpath"> <fileset dir="WEB-INF/lib" includes="*.jar"/> </path> <taskdef name="gen" classname="org.seasar.doma.extension.gen.task.Gen" classpathref="classpath" loaderref="loader"/> <typedef name="entityConfig" classname="org.seasar.doma.extension.gen.task.EntityConfig" loaderref="loader"/> <typedef name="daoConfig" classname="org.seasar.doma.extension.gen.task.DaoConfig" loaderref="loader"/> <typedef name="sqlConfig" classname="org.seasar.doma.extension.gen.task.SqlConfig" loaderref="loader"/> <target name="gen"> <gen dialectName="${dialectName}" driverClassName="${driverClassName}" url="${url}" user="${user}" password="${password}"> <entityConfig destdir="${javaDestDir}" packageName="${entityPackageName}" /> <daoConfig destdir="${javaDestDir}" packageName="${daoPackageName}" configClassName="${configClassName}" /> <sqlConfig destdir="${sqlDestDir}" /> </gen> </target> <target name="genTest"> <genTest dialectName="${dialectName}" driverClassName="${driverClassName}" url="${url}" user="${user}" password="${password}"> <sqlTestConfig destdir="${javaDestDir}" testClassName="${sqlTestClassName}"> <fileset dir="${sqlDestDir}"> <include name="${sqlFilePattern}"/> </fileset> </sqlTestConfig> </genTest> </target> </project>
実行する
↓
Buildfile: sample_spring_doma2a/doma-gen-build.xml gen: [gen] [DOMAGEN0017] 方言にはクラス[org.seasar.doma.extension.gen.dialect.MysqlGenDialect]が使用されます。 [gen] Wed Dec 21 00:17:40 JST 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification. [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/com/example/mshige1979/server/db/entity/Item.java [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/META-INF/com/example/mshige1979/server/db/dao/ItemDao/selectById.sql [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/com/example/mshige1979/server/db/entity/Memo.java [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/META-INF/com/example/mshige1979/server/db/dao/MemoDao/selectById.sql [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/com/example/mshige1979/server/db/entity/ObjData.java [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/META-INF/com/example/mshige1979/server/db/dao/ObjDataDao/selectById.sql BUILD SUCCESSFUL Total time: 1 second
できたもの(一部)
ItemDao.java
package com.example.mshige1979.server.db.dao; import com.example.mshige1979.server.db.config.AppConfig; import com.example.mshige1979.server.db.entity.Item; import org.seasar.doma.Dao; import org.seasar.doma.Delete; import org.seasar.doma.Insert; import org.seasar.doma.Select; import org.seasar.doma.Update; /** */ @Dao(config = AppConfig.class) public interface ItemDao { /** * @param id * @return the Item entity */ @Select Item selectById(Integer id); /** * @param entity * @return affected rows */ @Insert int insert(Item entity); /** * @param entity * @return affected rows */ @Update int update(Item entity); /** * @param entity * @return affected rows */ @Delete int delete(Item entity); }
Item.java
package com.example.mshige1979.server.db.entity; import java.time.LocalDateTime; import org.seasar.doma.Column; import org.seasar.doma.Entity; import org.seasar.doma.GeneratedValue; import org.seasar.doma.GenerationType; import org.seasar.doma.Id; import org.seasar.doma.Table; /** * */ @Entity(listener = ItemListener.class) @Table(name = "item") public class Item { /** */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") Integer id; /** */ @Column(name = "name") String name; /** */ @Column(name = "price") Integer price; /** */ @Column(name = "create_at") LocalDateTime createAt; /** */ @Column(name = "update_at") LocalDateTime updateAt; /** * Returns the id. * * @return the id */ public Integer getId() { return id; } /** * Sets the id. * * @param id the id */ public void setId(Integer id) { this.id = id; } /** * Returns the name. * * @return the name */ public String getName() { return name; } /** * Sets the name. * * @param name the name */ public void setName(String name) { this.name = name; } /** * Returns the price. * * @return the price */ public Integer getPrice() { return price; } /** * Sets the price. * * @param price the price */ public void setPrice(Integer price) { this.price = price; } /** * Returns the createAt. * * @return the createAt */ public LocalDateTime getCreateAt() { return createAt; } /** * Sets the createAt. * * @param createAt the createAt */ public void setCreateAt(LocalDateTime createAt) { this.createAt = createAt; } /** * Returns the updateAt. * * @return the updateAt */ public LocalDateTime getUpdateAt() { return updateAt; } /** * Sets the updateAt. * * @param updateAt the updateAt */ public void setUpdateAt(LocalDateTime updateAt) { this.updateAt = updateAt; } }