前回データモデルを作成して随分間が空いてしまったので今の状態のクラス図を作成しておく。
第1回で作成したデータモデルを修正してユーザーに紐付く情報を別のテーブルに持つようにする。(図1)
ユーザーと小説はN:Nの関係とする。関係の情報にお気に入りや評価を持つようにする。

変更に伴って関連するクラスも追加する。

コードの修正については「マルチユーザ対応 #9」で徐々に行っていこうと思う。
Technical Notes
前回データモデルを作成して随分間が空いてしまったので今の状態のクラス図を作成しておく。
第1回で作成したデータモデルを修正してユーザーに紐付く情報を別のテーブルに持つようにする。(図1)
ユーザーと小説はN:Nの関係とする。関係の情報にお気に入りや評価を持つようにする。

変更に伴って関連するクラスも追加する。

コードの修正については「マルチユーザ対応 #9」で徐々に行っていこうと思う。
Spring MVC等のフレームワークを使用するとHttpリクエストから自動的にJavaオブジェクトに変換してくれる。
@PutMapping
public String onSubmitByPutMethod(@Valid UserDetailsForm userDetailsForm, BindingResult result) throws IOException {
便利な機能だが下記の通りセキュリティリスクもあるので注意する。
例えばユーザー情報管理画面のリクエストをEntityクラスに設定するようにした場合、Httpリクエストを細工すると元の画面には無い項目をオブジェクトに設定出来てしまう。
そのままそのオブジェクトを永続化すれば、不正にパスワードや権限を変更出来てしまう。
@SortableFieldを付けるとそのFieldに対してソート用のインデックスが作成されるようになる。
@Field(analyze = Analyze.NO)
@SortableField
private String description;
但し、ソート可能とするFieldはトークン化してはいけない。そのため、FieldをAnalyze.NOとするか、Normalizerを指定する必要がある。(AnalyzerとNormalizerは排他的なのでNormalizerを指定すると結果的にAnalyze.NOになっている)
@NormalizerDef(name = "novelSort", filters = @TokenFilterDef(factory = LowerCaseFilterFactory.class))
public class Novel extends BaseObject implements Serializable {
@Field
@Field(name = "descriptionSort", normalizer = @Normalizer(definition = "novelSort"))
@SortableField(forField = "descriptionSort")
private String description;
実は@SortableFieldを指定しなくてもソート自体は出来るがインデックスが無いので、パフォーマンスが悪い。
Sort sort = new Sort(new SortField("descriptionSort", SortField.Type.STRING))
FullTextQuery query = ...
query.setSort(sort);
JaCoCoプラグインを使う。
https://docs.gradle.org/current/userguide/jacoco_plugin.html
build.gradleに下記を追記する。
plugins {
id 'jacoco'
}
jacocoTestReport {
reports {
xml.enabled false
csv.enabled false
html.destination file("${buildDir}/jacocoHtml")
}
}
これでタスクにjacocoTestReportを追加して実行すればレポートが作成されるようになる。
(デフォルトの出力先は$buildDir/reports/jacoco)
さらに、当プロジェクトではLombokを使用しているため、Lombokで自動生成されたコードをカバレッジの対象外にしたい。
プロジェクトのルートフォルダにlombok.configファイルを作成し、下記の通り記入することで対象外とすることが出来る。
lombok.addLombokGeneratedAnnotation = true
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-streaming
検索結果が格納されているStreamは使用後にcloseしないと駄目らしい。
return novelDao.findByUnreadTrueOrderByTitleAndNovelChapterId().collect(Collectors.toList());
上記を下記のように変更する必要がある。
try (Stream<Novel> novels = novelDao.findByUnreadTrueOrderByTitleAndNovelChapterId()) {
return novels.collect(Collectors.toList());
}
※2022/04/25追記 無料のSSL証明書をインストールする(Ubuntu 22.04版)を公開。
No-IPドメインでLet’s Encryptの証明書を使うことが出来る。
1.事前確認
・ドメイン名を取得していること(今回はNo-IPを使用する)
・80、443ポートが外部に開放されていること
2.Certbotをインストールする
yum install epel-release
yum install certbot python-certbot-apache
3.証明書取得する
certbot certonly --agree-tos --webroot -w /var/www/html/ -d hoge.no-ip.org
取得に成功した場合、証明書は以下に保存される。
ll /etc/letsencrypt/live/hoge.no-ip.org/
4.Apacheに証明書を設定し再起動する
バージョン2.4.7以前の場合
SSLCertificateFile /etc/letsencrypt/live/hoge.no-ip.org/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/hoge.no-ip.org/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/hoge.no-ip.org/chain.pem
バージョン2.4.8以降の場合
SSLCertificateFile /etc/letsencrypt/live/hoge.no-ip.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/hoge.no-ip.org/privkey.pem
systemctl restart httpd
5.証明書を自動的に更新する
証明書は90日で失効してしまうため、cron.dに以下のスクリプトを置いておく。
有効期限の30日前になると自動的に更新されるので、とりあえず毎日か、毎週実行する様にしておけば良い。
#!/bin/sh
/bin/certbot renew --webroot-path /var/www/html/ --post-hook "systemctl reload httpd"
SQLとバインド変数の値を両方ともログに出力するにはapplication.ymlに下記を追記する。
logging:
level:
org.hibernate:
SQL: debug
type:
EnumType: trace
descriptor.sql.BasicBinder: trace
Apache Tomcat 9.0.31にアップデートしたらAJP経由でアクセス出来なくなった。
server.xmlを見てみるとAJPの設定がコメントアウトされていたので、下記を追記した。
<Connector protocol="AJP/1.3"
port="8009"
redirectPort="8443"/>
しかし、今度は下記のようなエラーが出るようになった。
Caused by: java.lang.IllegalArgumentException: The AJP Connector is configured with secretRequired="true" but the secret attribute is either null or "". This combination is not valid.
at org.apache.coyote.ajp.AbstractAjpProtocol.start(AbstractAjpProtocol.java:264)
at org.apache.catalina.connector.Connector.startInternal(Connector.java:1035)
secretRequiredがデフォルトで”true”になっているため、secretを設定しろということらしい。
面倒なのでアドレスでアクセス制限をかけて、secretRequiredは”false”にした。
<Connector protocol="AJP/1.3"
address="localhost"
port="8009"
redirectPort="8443"
secretRequired="false"/>
fetch = FetchType.LAZYとしている場合、データベースを参照するときにSessionが切れていてLazyInitializationExceptionが発生することがある。
Caused by:
org.hibernate.LazyInitializationException:
failed to lazily initialize a collection of role:
crawlerapi.entity.Novel.novelChapters, could not initialize proxy - no Session
そのような場合は、application.ymlに以下を追加する。
jpa:
properties:
hibernate:
enable_lazy_load_no_trans: true
最新バージョンを確認する。
D:\>npm outdated
Package Current Wanted Latest Location
@vue/cli-plugin-babel 3.12.1 3.12.1 4.1.1 crawler-client
@vue/cli-plugin-eslint 3.12.1 3.12.1 4.1.1 crawler-client
@vue/cli-service 3.12.1 3.12.1 4.1.1 crawler-client
メジャーバージョンは、npm updateでは更新されないので、以下のコマンドを実行する。
D:\>vue upgrade
Gathering package information...
Name Installed Wanted Latest Command to upgrade
@vue/cli-service 3.12.1 3.12.1 4.1.1 vue upgrade @vue/cli-service
@vue/cli-plugin-babel 3.12.1 3.12.1 4.1.1 vue upgrade @vue/cli-plugin-babel
@vue/cli-plugin-eslint 3.12.1 3.12.1 4.1.1 vue upgrade @vue/cli-plugin-eslint
? Continue to upgrade these plugins? (Y/n) Y
他にも、
Package Current Wanted Latest Location
eslint 5.16.0 5.16.0 6.7.2 crawler-client
eslint-plugin-vue 5.2.3 5.2.3 6.0.1 crawler-client
sass-loader 7.3.1 7.3.1 8.0.0 crawler-client
などを個別にアップグレードする場合は、以下のコマンドを実行する。
D:\>npm install --save-dev sass-loader@8
以下のようにワーニングが出た場合は指示に従ってインストールする。
npm WARN sass-loader@8.0.0 requires a peer of node-sass@^4.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN sass-loader@8.0.0 requires a peer of fibers@>= 3.1.0 but none is installed. You must install peer dependencies yourself.
D:\>npm install --save-dev sass fibers