SQL Server2008, JDBC, PreparedStatement#setString()で設定した値がnvarchar(4000)扱いされる

同じような事例: http://www.thatsjava.com/jdbc/104532/(解決方法が書いてあるというリンクが買収の影響で無効……)

現象

タイトルどおり。

何が困るかというと、

select * from X
where X.code in (?,?,?,?, ... )

みたいなクエリを投げた場合。codeがvarchar型でパラメタがnvarchar型だと、インデクスを使ってくれずにすごく残念な実行計画と長い待ち時間が発生することになる。

解決方法

その1: sendStringParametersAsUnicode

接続時に、

sendStringParametersAsUnicode=false

を設定するとよさそうです。

sendStringParametersAsUnicode 接続文字列プロパティを設定することで、String パラメータを Unicode 以外の形式で SQL Server に渡し、パフォーマンスを向上させることができます。 sendStringParametersAsUnicode の既定の設定は "true" です。これは、String パラメータが Unicode として送信されることを示します。 sendStringParametersAsUnicode が "false" に設定されている場合、その接続のすべての String パラメータは、データベースの既定の照合順序でサーバーに送信されます。

http://technet.microsoft.com/ja-jp/library/ms378857(SQL.90).aspx

このオプションがTrueのとき、JDBCドライバはパラメータクエリで使われる文字列パラメータをUnicodeに変換して送信する。デフォルトはTrueになっているので注意。
falseのときはデフォルトエンコーディングで送信される。ドライバのドキュメントには「which can improve performance」とあるが、実際には100-1000倍の性能差にもなることがある。

http://www.isla-plata.org/wiki/pukiwiki.php?SQLServer%20JDBC%A5%C9%A5%E9%A5%A4%A5%D0%A4%C8SendStringParametersAsUnicode%A5%AA%A5%D7%A5%B7%A5%E7%A5%F3

ただし、nvarcharとして送信したい文字列までvarcharとなるので設定によっては文字化けするかも(詳細は未検証)。
PreparedStatement#setString/setNStringを適切に使い分ける必要がありそうですね。

その2: varcharが必要な場所でキャストする

select * from X
where X.code in (
  cast(? as varchar),
  cast(? as varchar),
  cast(? as varchar),
  cast(? as varchar),
  ...
)

めんどくさい?アマエンナコラー!努力!!!