【Java】No qualifying bean of type 'xxx' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations
テストを実行するとエラー
No qualifying bean of type 'xxx' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations
beanが読み込まれていないみたい?
web.xml
にコンポーネント追記し忘れていたので追加して解決。
<context:base-package="xxx" />
【MySQL】ユニークキー制約はついてないけど重複させたくない時にやったこと
実装環境
やりたいこと
ユーザ作成APIが同時に実行された際に、同じname
のユーザを登録させたくない。
以下のような user
テーブルがあり、name
にはユニークキー 制約はついていない。
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `age` int(11) NOT NULL, PRIMARY KEY (`id`) );
試したこと
2つのトランザクション内で同じクエリを発行し、動作を確認する。
1. SELECT FOR UPDATE
を使う。
BEGIN; SELECT * FROM user WHERE name='test' FOR UPDATE; INSERT INTO user( name, age ) VALUES ( 'test', 20 ); COMMIT;
この方法だと、トランザクション1で INSERT
後、トランザクション2で INSERT
するとデッドロックが発生してしまった。
デッドロック発生によりロールバックすることで、同じnameのユーザレコードは重複しない
という条件は満たせるが、デッドロックはあまりよろしくない。
2. NOT EXSITS
を使う。
BEGIN; INSERT INTO user( name, age ) SELECT 'test', 20 WHERE NOT EXISTS( SELECT * FROM user WHERE name='test' ); COMMIT;
この方法だとトランザクション1で INSERT
後、トランザクション2で INSERT
しても、1がcommitされるまで2のクエリは実行されない。
サブクエリでSELECT
をつけることにより、テーブルロック(インテンションロック)がかかっている様子。
結論
今回の場合は NOT EXISTS
でうまくいった。
ユニークキー制約をつければ、こんな複雑なクエリを実装する必要はないので、
可能であればユニークキー制約をつけ、deplicatedError
が発生したらロールバックするような挙動が望ましい。
7/16 追記
MySQL5.5の場合
なんとMySQL5.5で実装する必要があった。
MySQL5.5の場合、上記のNOT EXISTS
文ではsyntaxエラーが出てしまい使えない。
というわけで再度調べた。
BEGIN; INSERT INTO user(name, age) SELECT name, age FROM (SELECT 'test' AS name, 20 AS age ) AS u WHERE NOT EXISTS ( SELECT * FROM user WHERE name='test' ); COMMIT;
上記のSQLで成功。
MySQL5.5の場合、FROM
をつけて値を取得する場所を明示的に指定する必要があるようだ。
サブクエリにはaliasをつけないと怒られるので、
特に使用していないがAS u
をFROM
の後につけている。
参考:
【NGINX】phusion/passenger-dockerでレスポンスヘッダーを編集する
やりたいこと
HTTPのレスポンスヘッダーにnginxなどのバージョン情報を表示させないようにしたい。
調べるとwget
でnginxを取得して展開するような記述が多いが、
今回はdockerイメージに phusion/passenger
を使用しており、
イメージ内にnginxが含まれているため、別の方法で実装したい。
実行環境
- docker
- phusion/passenger-ruby26
- nginx 1.14.0
修正前のレスポンスヘッダー
< status: 302 Found < cache-control: no-cache ... < x-powered-by: Phusion Passenger 6.0.4 * < server: nginx/1.14.0 + Phusion Passenger 6.0.4 *
*のついたやつを消したい。
実装方法
- nginxのモジュールをインストールする。
# Dockerfile RUN apt-get update \ && apt-get install -y \ libnginx-mod-http-headers-more-filter \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
- 設定ファイルを作成する。
# headers_filter.conf server_tokens off; more_clear_headers 'Server' 'X-Powered-By';
server_tokens off
: nginxのバージョンを削除
more_clear_headers
: 指定した行を削除
- 設定ファイルをDockerの
/etc/nginx/conf.d
配下に配置する
ADD ./headers_filter.conf /etc/nginx/conf.d/headers_filter.conf
/etc/nginx/main.d
とかもありますが、conf.d
でないと動かなかったので、こちらに置いてください。
めちゃめちゃ詰まりましたが、これだけでOK。
そのほか
more-clear_header
を複数行に分けて書くと動かなかった。
# headers_filter.conf server_tokens off; more_clear_headers 'Server'; more_clear_headers 'X-Powered-By';
nginx-plus-module-headers-more
というモジュールは商用nginxでしか使えないらしい。
Railsアプリからdocker上のmysqlに接続する方法
Railsアプリからdocker上のmysqlに接続する際にエラーが出てしまったため、事象と解決策を備忘録として残す。
開発環境
事象
最初に書かれていたdatabase.ymlはこんな感じ。
# config/database.yml default: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: socket: /var/run/mysqld/mysqld.sock
この記述だと rails db:create
時にエラーが出てしまった。
Mysql2::Error::ConnectionError: Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
mysql側でsocketの値を確認してみる。
mysql> show variables like '%socket%'; +-----------------------------------------+-----------------------------+ | Variable_name | Value | +-----------------------------------------+-----------------------------+ | performance_schema_max_socket_classes | 10 | | performance_schema_max_socket_instances | -1 | | socket | /var/run/mysqld/mysqld.sock | +-----------------------------------------+-----------------------------+
こちらは問題なさそう。
解決策
database.ymlにホストを指定する記述を追加して解決。
# config/database.yml default: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: socket: /var/run/mysqld/mysqld.sock host: 127.0.0.1 <-- ここを追加
これがないと、dockerを見に行ってくれないよう。
Kubernetes
manifest
Service:
StatefulSet: 一意で永続的な ID と固有のホスト名を持つ [Pods] のセットを表す
メリット: pod再作成後も同じホスト名を維持するCronJob: 時間ベースのスケジュールでJobを作成する
Job: Jobは1つ以上のPodを作成し、指定された数が正常に終了した際にJobが完了となる
kubectl create -f [ファイル名]
でJobを手動実行できる
Pod:
- Image: GCRを使用する場合、イメージタグやイメージダイジェスト(sha256でハッシュ化された一意の値)で、イメージを指定することができる
pod
で読み込むimageID
を変更したい場合は、ダイジェストをimage
に指定する
- Image: GCRを使用する場合、イメージタグやイメージダイジェスト(sha256でハッシュ化された一意の値)で、イメージを指定することができる
# タグ image: gcr.io/xxx/xxx:latest
# ダイジェスト image: gcr.io/xxx/xxx@sha256:xxxxxxxxxxxxxxxx
- ImagePullPolicy
- IfNotPresent: ローカルでイメージが見つからない場合にのみイメージをpullする
- Always: Podの起動時に常にイメージをpullする
env: 環境変数を定義する
Volumes
- Podやコンテナが削除された場合でも、データを保持してくれる
- Pod内のコンテナ間でデータの共有ができる
- emptyDir: Podが削除された際にデータも削除する
- ボリューム名が一意であれば、複数のボリュームを定義することもできる
第6章 Kubernetes におけるストレージのプロビジョニング - Red Hat Customer Portal
PersistentVolume: 永続ボリューム
InitContainers
- 同じPod内の
containers
で指定したコンテナが起動する前に初期化処理を目的として起動することができる - セキュリティの理由からアプリケーションコンテナに含めたくないユーティリティーを含んだり実行できる
- アプリケーションコンテナの実行の前に起動が完了する
- 同じPod内の
Security Context: コンテナに対して権限やアクセス制御の設定をする
- runAsUser: コンテナのプロセスのUIDを指定する
BackendConfig(GKE)
- アクセスにIP制限をかける
- ServiceのAnnotationsに beta.cloud.google.com/backend-config を記述して利用する
- CronJob: 時間ベースのスケジュールでJobを作成する
- concurrencyPolicy: 古いJobがまだ動いてる際に、新しいJobを実行するかどうかを設定する
- Allow(default):同時実行に対して制限を行わない
- concurrencyPolicy: 古いJobがまだ動いてる際に、新しいJobを実行するかどうかを設定する
- Job:
- 1つ以上のPodを作成し、指定された数が正常に終了するのを保証する
- 指定された数が正常に終了した際にJobが完了となる
- Jobを削除するとそのJobによって作成されたPodも削除される
- restartPolicy:
- Never:
Jobが失敗した際、コンテナを再起動しない
PodがErrorになった際にはJobが新しいPodを作成して実行する
- Never:
- backoffLimit: エラー時の再試行回数を指定する
- activeDeadlineSeconds: Jobの開始時点からの有効制限時間を秒数で指定する
- Ingress:
- rules: バックエンドへ振り分ける際のルールを定義する
- host: HTTP HOSTヘッダの値が一致した場合に振り分ける
- backend: 振り分け先を指定
- host: HTTP HOSTヘッダの値が一致した場合に振り分ける
- defaultBackend: ルールに一致しないリクエストの振り分け先を定義する
- rules: バックエンドへ振り分ける際のルールを定義する
- NodeAffinity/NodeAntiAffinity: PodがどのNodeにスケジュールされるかを制限する(NodeSelectorと同じ)
- nodeSelectorTerms: podをデプロイするノードの条件を定義する
- PodAffinity: 指定されたラベルに一致するPodが動作しているNodeがもつ
topologyKey
が同一のNodeにPodを配置させる PodAntiAffinity:
topologyKey
の範囲内に指定されたラベルに一致するPodが動作していなければ、topologyKey
範囲内のNodeのいずれかにPodを配置するClusterRoleBinding: ロールとユーザを紐付ける
コマンド
- コンテナ内のファイルをホストにコピー
kubectl cp some_container:/path/to/target_file ./target_file
- ホスト上のファイルをコンテナ内の指定ディレクトリにコピー
$ kubectl cp target_file some_container:/path/to/target_dir/
Apache Camel
【JUnit5】DBUnitを使ってDaoのテストを実装する
難しくて1週間くらいかかりました・・・。
実装環境
- Ubuntu 18.04(LTS)
- PostgreSQL
- Maven
- Spring MVC
- Junit5
- DBunit
- JdbcTemplate
テストしたいコード
@Repository public class UserDaoImpl implements UserDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public List<UserModel> getUser(Integer id) { List<UserModel> list = jdbcTemplate.query("SELECT * FROM users WHERE id=?", new Object[]{id}, new RowMapper<UserModel>() { public UserModel mapRow(ResultSet resultSet, int rowNum) throws SQLException { UserModel user = new UserModel(); user.setId(resultSet.getInt("id")); user.setName(resultSet.getString("name")); return user; } }); return list; } }
idでSELECTしたuserのリストを返却する簡単なもの
事前準備
テスト用のDBを用意しておく
database name | user | password |
---|---|---|
springmvc_test | pgadmin | pgadmin |
column | type |
---|---|
id | integer |
name | varchar(16) |
実装手順
ライブラリを追加
pom.xml
を編集する
<dependency> <groupId>org.dbunit</groupId> <artifactId>dbunit</artifactId> <version>2.6.0</version> <scope>test</scope> </dependency> <dependency> <groupId>com.github.springtestdbunit</groupId> <artifactId>spring-test-dbunit</artifactId> <version>1.3.0</version> <scope>test</scope> </dependency>
テスト用コンテキストファイルを作成する
// src/main/webapp/WEB-INF/test-context.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" 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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:annotation-driven/> <context:component-scan base-package="com.springMvcTutorial.controllers"/> <context:component-scan base-package="com.springMvcTutorial.dao" /> <context:component-scan base-package="com.springMvcTutorial.models" /> <context:component-scan base-package="com.springMvcTutorial.services" /> // テスト用のDBを設定する <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.postgresql.Driver" /> <property name="url" value="jdbc:postgresql://localhost:5432/springmvc_test" /> <property name="username" value="pgadmin" /> <property name="password" value="pgadmin" /> </bean> <bean class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg ref="dataSource" /> </bean> </beans>
テスト用DBにインサートするxmlファイルを用意する
// src/test/resources/data/insert_test_user.xml <?xml version="1.0" encoding="UTF-8"?> <dataset> <USERS ID="10" NAME="testUser" /> </dataset>
テストクラスを実装する
@SpringJUnitWebConfig(locations = { "file:src/main/webapp/WEB-INF/test-context.xml"} ) @TestExecutionListeners({ ServletTestExecutionListener.class, DirtiesContextBeforeModesTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionDbUnitTestExecutionListener.class}) class UserDaoImplTest { @Autowired private WebApplicationContext webApplicationContext; private JdbcTemplate jdbcTemplate; @Inject private UserDaoImpl userDao; @Test @DatabaseSetup("/data/insert_test_user.xml") @DisplayName("getUser on userDaoImpl") void getUser() throws Exception { UserModel user = new UserModel(); user.setId(10); user.setName("testUser"); List<UserModel> users = Arrays.asList(user); List<UserModel> resultWithUser = userDao.getUser(10); List<UserModel> resultNoUser = userDao.getUser(1); assertEquals(resultWithUser.size(), 1); assertEquals(resultWithUser, users); assertEquals(resultNoUser.size(), 0); } }
解説
- テスト用のコンテキストファイルパスを指定する
@SpringJUnitWebConfig(locations = { "file:src/main/webapp/WEB-INF/test-context.xml"} )
- JdbcTemplateをインジェクトする
@Autowired private WebApplicationContext webApplicationContext; private JdbcTemplate jdbcTemplate;
- テスト対象のクラスをインジェクトする
@Inject private UserDaoImpl userDao;
@DatabaseSetup("/data/insert_test_user.xml")