Mengambil ParameterMetaData melalui useFmtOnly

Unduh driver JDBC

Driver Microsoft JDBC untuk SQL Server menyertakan cara alternatif untuk mengkueri Metadata Parameter dari server, gunakanFmtOnly. Fitur ini pertama kali diperkenalkan dalam driver versi 7.4, dan diperlukan sebagai solusi untuk masalah yang diketahui di sp_describe_undeclared_parameters.

Driver terutama menggunakan prosedur sp_describe_undeclared_parameters tersimpan untuk mengkueri Metadata Parameter. Prosedur ini adalah pendekatan yang direkomendasikan untuk pengambilan Metadata Parameter dalam sebagian besar keadaan. Namun, menjalankan prosedur tersimpan saat ini gagal di bawah kasus penggunaan berikut:

  • Terhadap kolom Always Encrypted
  • Terhadap tabel sementara dan variabel tabel
  • Terhadap tampilan

Solusi yang diusulkan untuk kasus penggunaan ini adalah mengurai kueri SQL pengguna untuk parameter dan target tabel, lalu menjalankan SELECT kueri dengan FMTONLY diaktifkan. Cuplikan berikut akan membantu memvisualisasikan fitur.

--create a normal table 'Foo' and a temporary table 'Bar'
CREATE TABLE Foo(c1 int);
CREATE TABLE #Bar(c1 int);

EXEC sp_describe_undeclared_parameters N'SELECT * FROM Foo WHERE c1 = @p0' --works fine
EXEC sp_describe_undeclared_parameters N'SELECT * FROM #Bar WHERE c1 = @p0' --fails with "Invalid object name '#Bar'"

SET FMTONLY ON;
SELECT c1 FROM Foo; --works
SET FMTONLY OFF;
SET FMTONLY ON;
SELECT c1 FROM #Bar; --works
SET FMTONLY OFF;

Mengaktifkan/menonaktifkan fitur

Fitur useFmtOnly nonaktif secara default. Pengguna dapat mengaktifkan fitur ini melalui string koneksi dengan menentukan useFmtOnly=true. Misalnya: jdbc:sqlserver://<server>:<port>;encrypt=true;databaseName=<databaseName>;user=<user>;password=<password>;useFmtOnly=true;.

Fitur ini juga tersedia melalui SQLServerDataSource.

SQLServerDataSource ds = new SQLServerDataSource();
ds.setServerName(<server>);
ds.setPortNumber(<port>);
ds.setDatabaseName("<databaseName>");
ds.setUser("<user>");
ds.setPassword("<password>");
ds.setUseFmtOnly(true);
try (Connection c = ds.getConnection()) {
    // do work with connection
}

Fitur ini juga tersedia pada tingkat Pernyataan. Pengguna dapat mengaktifkan/menonaktifkan fitur melalui PreparedStatement.setUseFmtOnly(boolean).

Catatan

Driver akan memprioritaskan properti tingkat Pernyataan di atas properti Tingkat koneksi.

Menggunakan fitur

Setelah diaktifkan, driver akan mulai menggunakan fitur baru secara internal alih-alih saat mengkueri sp_describe_undeclared_parameters Metadata Parameter. Tidak ada tindakan lebih lanjut yang diperlukan dari pengguna akhir.

final String sql = "INSERT INTO #Bar VALUES (?)";
try (Connection c = DriverManager.getConnection(URL, USERNAME, PASSWORD)) {
    try (Statement s = c.createStatement()) {
        s.execute("CREATE TABLE #Bar(c1 int)");
    }
    try (PreparedStatement p1 = c.prepareStatement(sql); PreparedStatement p2 = c.prepareStatement(sql)) {
        ((SQLServerPreparedStatement) p1).setUseFmtOnly(true);
        ParameterMetaData pmd1 = p1.getParameterMetaData();
        System.out.println(pmd1.getParameterTypeName(1)); // prints int
        ParameterMetaData pmd2 = p2.getParameterMetaData(); // throws exception, Invalid object name '#Bar'
    }
}

Catatan

Fitur ini hanya mendukung SELECT/INSERT/UPDATE/DELETE kueri. Kueri harus dimulai dengan salah satu dari 4 kata kunci yang didukung atau Ekspresi Tabel Umum diikuti oleh salah satu kueri yang didukung. Parameter dalam Ekspresi Tabel Umum tidak didukung.

Masalah yang diketahui

Saat ini ada beberapa masalah dengan fitur ini, yang disebabkan oleh celah dalam logika penguraian SQL. Masalah ini dapat diatasi dalam pembaruan fitur di masa mendatang, dan di dokumentasikan di bawah ini bersama dengan saran solusi.

J. Menggunakan alias 'teruskan dideklarasikan'

CREATE TABLE Foo(c1 int)

DELETE fooAlias FROM Foo fooAlias WHERE c1 > ?; --Invalid object name 'fooAlias'

--Workaround #1: Specify AS keyword
DELETE fooAlias FROM Foo AS fooAlias WHERE c1 > ?;
--Workaround #2: Use the table name
DELETE Foo FROM Foo fooAlias WHERE c1 > ?;

B. Nama Kolom ambigu saat tabel memiliki nama kolom bersama

CREATE TABLE Foo(c1 int, c2 int, c3 int)
CREATE TABLE Bar(c1 int, c2 int, c3 int)

SELECT c1,c2 FROM Foo WHERE c3 IN (SELECT c3 FROM Bar WHERE c1 > ? and c2 < ? and c3 = ?); --Ambiguous Column Name

--Workaround: Use aliases
SELECT c1,c2 FROM Foo WHERE c3 IN (SELECT c3 FROM Bar b WHERE b.c1 = ? and b.c2 = ? and b.c3 = ?);

C. SELECT dari subkueri dengan parameter


CREATE TABLE Foo(c1 int)

SELECT * FROM (SELECT * FROM Foo WHERE c1 = ?) WHERE c1 = ?; --Incorrect syntax near '?'

--Workaround: N/A

D. Subkueri dalam klausa SET

CREATE TABLE Foo(c1 int)

UPDATE Foo SET c1 = (SELECT c1 FROM Foo) WHERE c1 = ?; --Incorrect syntax near ')'

--Workaround: Add a 'delimiting' condition
UPDATE Foo SET c1 = (SELECT c1 FROM Foo HAVING (HASH JOIN)) WHERE c1 = ?;

Lihat juga

Mengatur properti koneksi