親バカエンジニアのナレッジ帳

webのエンジニアをやっており、日頃の開発で詰まったことや書き残しておきたいことを載せています。育児のイロハという育児サイト(https://ikujip.jp)の開発も行っているため、その開発で使用されている技術についても掲載しています。

Vue.jsでビルド時にgzipファイルを出力する方法

出力ファイルは極力圧縮したい

Webサービスにおいて、コンテンツ内容や機能面の充実度が重要であることは言うまでもありませんが、ページのパフォーマンスも重要ですよね。
当然遅いより早い方が良いに決まっています。

今回はパフォーマンス改善策の1つである、ファイルの圧縮についてです。
正直ファイルの圧縮は違いが体感できるほど大きな効果は期待できないものですが、工数をあまりかけずに対応できるなら対応しておいた方が良いでしょう。

GoogleのPageSpeed Insightsも圧縮しろってうるさいですしね…w

Vue.jsでのnpmビルド時に同時に圧縮

Vue.jsでは、ビルド時(コンパイル時)に圧縮したい場合、非常に簡単にできるため、その方法を共有します。

今回はhtmlファイル、cssファイル、jsファイルのみをgzip化します。
画像などは頻繁に変更が起きないので、必要があれば随時手動で行えば良いですし、毎回gzip化させるのは無駄に時間がかかるので。


手順

まずは、vue/config/index.jsで以下のパラメータをtrueにします。

vue/config/index.js

productionGzip: true,

これだけ見ると、もうgzipに圧縮できてしまいそうですよね。

意気揚々とこの状態で
npm run buildすると

Error: Cannot find module 'compression-webpack-plugin’

と早速エラーが。
エラー内容を見ると、compression-webpack-pluginのモジュールがないと言っていますね。
そういえばvue/config/index.jsのproductionGzipの上にもこんな文言が

// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin

compression-webpack-pluginをインストールしてって書いてましたね。

ということで、目的のモジュールをインストールします。

npm install --save-dev compression-webpack-plugin


さあ、これで今度こそ大丈夫だろうと再度ビルドを実行してみると、
以下のようなエラーが

/********/src/main/vue/node_modules/compression-webpack-plugin/node_modules/schema-utils/src/validateOptions.js:32
    throw new ValidationError(ajv.errors, name);
    ^

ValidationError: Compression Plugin Invalid Options

options should NOT have additional properties

色々調べてみると、どうやらwebpackConfigのpluginsに設定の追加が必要らしく、webpack.prod.conf.jsに(webpack.base.conf.jsでも大丈夫です。環境によってどちらかに入れてください。)以下を追加しました。

webpack.prod.conf.js

// 上の方の変数宣言してるあたりに追加
var CompressionPlugin = require('compression-webpack-plugin’)



var webpackConfig = merge(baseWebpackConfig, {
     ・
     ・
     ・
  plugins: [
     ・
     ・
     ・
    new CompressionPlugin() // ここに追加
  ]
})

これで良いだろうと実行してみたのですが、再度上記と同じエラーが出ました。
なかなかうまくいきませんね。。。

ここはだいぶハマりましたが、解決法としては、先ほどインストールしたcompression-webpack-pluginのバージョンを下げれば上手くいきました。
ということで、2.0.0から1.1.11に下げてみます。
package.jsonの、

"compression-webpack-plugin": “^2.0.0”,

の箇所を

"compression-webpack-plugin": "^1.1.11”,

に変更し、以下コマンドの実行です。

npm install

さあ、これでビルドしてみましょう。

npm run buildの実行結果
npm run buildの実行結果

jsやcss、htmlのgzファイルが作られていることがわかりますね。
ついにgzip化に成功です!

サーバ側でgzipを読み込む設定にする

さて、みなさんご存知とは思いますが、gzip化できたと言っても、ただディレクトリ上にgzファイルが存在していれば読み込んでくれるというわけではありません。

ApacheやNginx、または.htaccessで、gzipファイルを読み込むよう設定する必要があるので、gzファイルができたからと言って安心せずにサーバ側の設定も変更してください。

Spring Data JPAでパラメータ以外でコロンを使いたい

RepositoryにSQLを直接記入

Spring Data JPAでORマッパーでは書けないようなSQLを実行したい場合、Repositoryに直接構文を書きますよね。
そんな時、パラメータで変数の値を渡したい時はコロンで変数を指定しますよね。
例えば以下のidのように

@Query(value = "SELECT * FROM test_tables WHERE id = :id ", nativeQuery = true)
List<Test> findById(@Param("id") Integer id);

パラメータ以外でコロンを使いたい

パラメータで変数を渡したい場合は普通にコロンをつければいいですが、それ以外の用途でコロンをつけたい場合はそうはいきません。
例えば以下のようなSQLをそのまま書くと、以下のようなエラーが発生します。
プログラムの中でこのようなSQLを実行する人はいないと思いますが笑、1から50まで表示されるSQLです。

SELECT 0 generate_series FROM DUAL WHERE (@num:= 1 - 1) * 0 UNION ALL
SELECT @num:= @num + 1 FROM information_schema.COLUMNS LIMIT 50

Repositoryに入れるとこんな感じですね。

@Query(value = "SELECT 0 generate_series FROM DUAL WHERE (@num:= 1 - 1) * 0 UNION ALL " +
"SELECT @num:= @num + 1 FROM information_schema.COLUMNS LIMIT 50", nativeQuery = true)
List<Integer> getNumber50();


エラー内容は以下です。

Space is not allowed after parameter prefix ‘:'

解決方法はエスケープ

上記エラーの解決方法としては、コロンの前にエスケープのスラッシュを入れることです。
ただし1つ入れるだけではダメで、以下のように2つのスラッシュを入れましょう。

@Query(value = "SELECT 0 generate_series FROM DUAL WHERE (@num\\:= 1 - 1) * 0 UNION ALL " +
"SELECT @num\\:= @num + 1 FROM information_schema.COLUMNS LIMIT 50", nativeQuery = true)
List<Integer> getNumber50();

Fire TV Stick

Fire TV Stick

新登場  Fire TV Stick 4K - Alexa対応音声認識リモコン付属

新登場 Fire TV Stick 4K - Alexa対応音声認識リモコン付属

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Spring Data JPA のfind+OrderByで、No property desc foundエラー時の対処法

Spring Data JPAでのOrderBy

Spring Data JPAでエンティティクラスにfindして複数のレコードを取得する場合、OrderByをつければ並び順を変更できることはご存知でしょうか?

findByNameOrderById(Spring name)

などとすれば、nameで絞り込んだ上で、idの降順で並び替えができます。

これを見る限り、後ろにOrderByを入れればいいんだな、と思ってしまいがちですが、以下のような書き方だとエラーが発生してしまいます。

findAllOrderById()

これだとコンパイルのタイミングで、

No property desc found for type Integer! Traversed path

のようなエラーメッセージが出力されるのです。

whereを指定しない場合、OrderByの前にByが必要

上記のfindAllの例のように、where句の絞り込みをしない場合、OrderByの前にもByが必要になります。

よって、findAllの場合は以下のようにしなければいけません。

findAllByOrderById()

SQLの書き方に慣れていると、order byの前にbyを付けているようで少し気持ち悪いですよね。

今回はfindAllの例でしたが、この書き方はfindFirstやfindTopの場合なども同様です。

findFirst1ByOrderById()
findTopByOrderById()

今まで条件を指定しないとOrderByが使えないのでは?と思われていた方がいたら、ぜひ参考にしてください。

Fire TV Stick

Fire TV Stick

新登場  Fire TV Stick 4K - Alexa対応音声認識リモコン付属

新登場 Fire TV Stick 4K - Alexa対応音声認識リモコン付属

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Vue.jsのv-ifやv-forで無駄にタグを増やしたくない時はtemplateで代用するのが便利

Vue.jsでの分岐やループ処理

Vue.jsでは分岐やループ処理で、動的にタグを描画したい場面がありますよね。
例えばリストを動的に増やしたい時は、liタグにv-forを付与したり、ある文言が特定の条件を満たした時のみ表示させる場合はpタグにv-ifを付けたりなど…。

例)

<ul>
  <li v-for="test in testList"></li>
</ul>

<p v-if="showMessage === true"></p>

それほど複雑でなければ困らないのですが、条件が複雑になったり、ループの階層が増える場合、v-forやv-if用に無駄にタグを増やしてしまうことはありませんか?

そんな時、無駄に階層を増やさなくてもできる方法があるのです。

templateタグを使う

タイトルにもある通りですが、templateタグを使えば万事解決です。

例えば以下のような使い方をした場合、実際に表示されるタグを見てみましょう。
(下記のような場合はspanタグにv-ifを入れてしまえばいいと思いますが、例としてtemplateタグを使っています。)

入力内容

<template v-if="showMessage === true">
  <span>メッセージ</span>
</template>

実際の表示

<span>メッセージ</span>

わかりますか?
templateタグは表示されませんよね。
MVCモデルで、サーバサイドからレンダリングした値をビューに表示させる時のような使い方ができるのです。

よって以下のように複数の階層のループ処理があっても、無駄にタグを増やす必要がないのです。

入力内容

<ul>
  <template v-for="test in testList">
    <li v-for="sub in test.subList">
      <span>{{ sub.message }}</span>
    </li>
  </template>
</ul>

実際の表示

<ul>
  <li>
    <span>メッセージが表示されます</span>
  </li>
</ul>

とても便利ですよね。
無駄にタグを増やさないようにぜひ使ってみてください。

Fire TV Stick

Fire TV Stick

新登場  Fire TV Stick 4K - Alexa対応音声認識リモコン付属

新登場 Fire TV Stick 4K - Alexa対応音声認識リモコン付属

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Spring Bootで実行SQLのログを取得する方法

アプリケーション開発でのSQLログ

アプリケーションの開発をしている時、実行されたSQLのログを参照したい場面がありますよね。
想定外のSQLが実行されていないか確認したり、ボトルネックとなっているSQLを確認したりなど。
Spring BootではそのようなSQLの実行ログを簡単に見ることができます。

今回の方法は、Spring Data JPAでデータベースへのアクセスをするものとします。

application.ymlでの設定

手順としては、application.ymlに以下を書き込むだけです。

logging:
  level:
    org:
      hibernate:
        SQL: DEBUG
        type:
          descriptor:
            sql:
              BasicBinder: TRACE

Spring Data JPAは内部的にhibernateで実装されているために、hibernateのログ設定になるんですね。
あとは通常通りサーバを起動させればOKです。
実際に処理を動かしてみると、以下のようなログが出力されることでしょう。

2018-10-16 17:59:53.321 DEBUG 14383 --- [nio-8080-exec-2] org.hibernate.SQL                        : SELECT ...
2018-10-16 17:59:53.321 TRACE 14383 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [INTEGER] - [1]

とても簡単ですよね。
O/Rマッパーでのオブジェクトへのアクセスで、実際にどのようなクエリが実行されているのかを確認したい場合などに設定してみてください。

Fire TV Stick

Fire TV Stick

新登場  Fire TV Stick 4K - Alexa対応音声認識リモコン付属

新登場 Fire TV Stick 4K - Alexa対応音声認識リモコン付属

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Vue.jsでURLの#(シャープ)を取り除く方法

URLの#(シャープ)

Vue-cliでvue.jsのセットアップを行った時、開発環境のURLを叩くと後ろに#(シャープ)が付いてしまいます。

http://localhost:8080と入力しても、http://localhost:8080/#/
となってしまいます。

これはhashモードという状態で付くものであり、これはこれでメリットがあるのですが(ここでは詳細は省きます)、このような状態で公開するのは格好悪いですよね。

historyモードにすることで解決

上記の#(シャープ)は、routerの設定でhistoryモードにすることで解決します。

例として、変更前のrouterの設定が以下であるとした場合に、

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Top',
      component: Top
    },
    {
      path: '/test',
      name: 'Test',
      component: Test
    }
  ]
})

以下のように mode: 'historyを入れれば解決です。

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'Top',
      component: Top
    },
    {
      path: '/test',
      name: 'Test',
      component: Test
    }
  ]
})

historyモードの注意点が1つあり、サーバ側のルーティング指定を適切に行わないと、http://localhost:8080/testとした場合に404エラーが発生してしまいます。

.htaccessの設定を変えるなど解決方法は調べれば出てくるのでここでは省きますが、後々別なところで適切なルーティングを行うための設定が必要になるということは留意してください。

Fire TV Stick

Fire TV Stick

新登場  Fire TV Stick 4K - Alexa対応音声認識リモコン付属

新登場 Fire TV Stick 4K - Alexa対応音声認識リモコン付属

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Spring BootでEntityオブジェクトのクローンをしてみる

Entityオブジェクトのクローン

SpringBootで機能の実装をしている中で、Entityオブジェクトのクローンを作りたくなることはありませんか?

例えばコピー機能などを想像してみてください。
いちいちオブジェクトをnewして、同じ値を全てsetして保存する...そんな方法でもできますが、綺麗なやり方ではないですよね。
属性が多ければsetの漏れも出るかもしれません。
そんな時、Entityオブジェクトのcloneを使えば、楽にクローンを作ることができます。

クローンをしたいEntityクラスにCloneableインターフェースを実装

例えばtestEntityをクローンする場合、まずは以下のようにCloneableインターフェースを継承します。

@Entity
public class testEntity implements Cloneable {

これを忘れると、後々以下のようなエラーが発生するので気をつけてください。

CloneNotSupportedException

そして以下のようにEntityにcloneメソッドを追加します。
※引数の「User user」や各種setはもちろんなくても大丈夫です。あくまで以下のようなことができるという参考にしてください。

public testEntity clone(User user) throws CloneNotSupportedException {
	TestEntity cloneTestEntity = (TestEntity) super.clone();
	cloneTestEntity.setId(null); // 新規のエンティティの場合はidをnullにしておきましょう。そうしないとクローン元のオブジェクトの上書きになってしまいます。
	cloneTestEntity.setCreatedUser(user); // 例えばこのuserのように引数をセットすることが可能です。
	cloneTestEntity.setCreatedAt(new Date());
	return cloneTestEntity;
}

クローンメソッドの呼び出し

あとは以下のようにcloneメソッドを呼び出せばクローンを取得することが可能になります。
originalTestEntityはクローン元のオブジェクトになります。

TestEntity testEntity = originalTestEntity.clone(user);

Fire TV Stick

Fire TV Stick

新登場  Fire TV Stick 4K - Alexa対応音声認識リモコン付属

新登場 Fire TV Stick 4K - Alexa対応音声認識リモコン付属

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB