barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)
Показаны сообщения с ярлыком JDBC. Показать все сообщения
Показаны сообщения с ярлыком JDBC. Показать все сообщения

понедельник, 18 января 2016 г.

JDBC, MS SQL Server и выполнение нескольких SQL-выражений одновременно

Проблема

Пытаюсь выполнить PreparedStatement, состоящий из 3 выражений:
DECLARE @myVar MyTableType;
INSERT INTO @myVar(a,b) VALUES(1,2);
EXECUTE someProc @myVar;
Т.е. объявляется переменная пользовательского табличного типа, в нее вставляется строка данных, после чего эта переменная передается в некоторую процедуру. 
Выполнение PreparedStatement производится методом execute().
Процедура внутри себя содержит инструкцию RAISERROR, которая в некоторых ситуациях генерирует ошибку. Проблема заключается в том, что если перед вызовом процедуры нет INSERT-выражения, RAISERROR приводит к появлению SQLException при выполнении execute(). Если же INSERT присутствует, то RAISERROR не приводит к SQLException в вызывающем коде, и execute() выполняется без ошибок.

Причина

Данное PreparedStatement генерирует несколько результатов, поскольку содержит несколько генерирующих результат выражений внутри себя (конструкция DECLARE не считается). Первый результат - это результат выполнения INSERT'а в переменную, который выполняется успешно, поэтому execute() не генерирует исключение, т.к. "курсор" установлен на первом результате. При попытке же сделать getMoreResults() для объекта PreparedStatement мы как раз и ловим ожидаемый SQLException, т.к. выполняется переход к результату выполнения команды EXECUTE, а этот EXECUTE у нас выполнился с ошибкой.

вторник, 25 марта 2014 г.

Weblogic: password-encrypted в конфиге jdbc-data-source и ошибка "Could not perform unpadding: invalid pad byte"

Проблема:

Есть некий EAR-ник, содержащий jdbc-data-source. В нем пароль задан с помощью password-encrypted:
<password-encrypted>{AES}q+rhp78+7777woah7777JW7777S+0p7777djmw7777c=</password-encrypted>
При старте приложения в лог валится ошибка:
####<Mar 19, 2014 4:44:21 PM MSK> <Error> <Console> <xxx> <AdminServer> <[ACTIVE] ExecuteThread: '21' for queue: 'weblogic.kernel.Default (self-tuning)'> <weblogic> <> <> <1395233061606> <BEA-240003> <Console encountered the following error weblogic.application.WrappedDeploymentException: Could not perform unpadding: invalid pad byte.
at com.rsa.jsafe.c.a(Unknown Source)
at com.rsa.jsafe.JSAFE_SymmetricCipher.decryptFinal(Unknown Source)
at weblogic.security.internal.encryption.JSafeEncryptionServiceImpl.decryptBytes(JSafeEncryptionServiceImpl.java:124)
at weblogic.security.internal.encryption.JSafeEncryptionServiceImpl.decryptString(JSafeEncryptionServiceImpl.java:184)
at weblogic.security.internal.encryption.ClearOrEncryptedService.decrypt(ClearOrEncryptedService.java:96)
at sun.reflect.GeneratedMethodAccessor378.invoke(Unknown Source)
... at weblogic.deploy.service.internal.targetserver.DeploymentReceiverCallbackDeliverer$1.run(DeploymentReceiverCallbackDeliverer.java:46)
at weblogic.work.SelfTuningWorkManagerImpl$WorkAdapterImpl.run(SelfTuningWorkManagerImpl.java:528)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:209)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:178)
При этом само приложение не стартует.

Причина


Конфиг datasource'а был перевезен с другого инстанса weblogic (соответственно, и сам зашифрованный пароль был создан именно для того инстанса). Как оказалось, шифрование пароля привязано к домену сервера Weblogic (о чем, кстати, и сказано в документации: http://docs.oracle.com/cd/E11035_01/wls100/admin_ref/utils.html#encrypt). Так что при копировании конфигурации в другой домен Weblogic требуется повторить процедуру шифрования пароля (как это делается, я писал тут: http://barbitoff.blogspot.ru/2013/11/weblogic-datasource.html).

воскресенье, 26 января 2014 г.

Pentaho: подключение к БД Oracle по Service Name

При создании "Database connection" в Pentaho при выборе СУБД Oracle нет возможности выбрать, как подключаться к БД - по SID или по Service Name. Есть только единственное поле - "Database". При этом, если ввести в него просто значение Service Name, на некоторых серверах подключение проходит успешно, тогда как на других приводит к ошибке:
2014/01/25 20:06:27 - ERROR (version 4.4.0-stable, build 17588 from 2012-11-21 16.02.21 by buildguy) : Error connecting to database: (using class oracle.jdbc.driver.OracleDriver)
2014/01/25 20:06:27 - ERROR (version 4.4.0-stable, build 17588 from 2012-11-21 16.02.21 by buildguy) : Listener refused the connection with the following error:ORA-12505, TNS:listener does not currently know of SID given in connect descriptor
Из текста ошибки видно, что СУБД считает, что подключение ведется по SID, а не по Service Name. Почему ошибка возникает лишь на некоторых серверах - не знаю, видимо это зависит от каких-то настроек на стороне сервера.
Почему СУБД считает, что подключение ведется именно по SID, понятно: форматы JDBC-URL'а при подключении по SID и по Service Name различаются. Для SID URL выглядит так (используется thin-драйвер):
jdbc:oracle:thin:@server:1521:mysid
, а для Service Name - так:
jdbc:oracle:thin:@server:1521/myservicename
Pentaho использует первый формат, что и ведет к ошибке. Однако, не все так плохо: если указать в начале значения поля "Database" прямой слеш (перед значением Service Name) подключение проходит успешно. Спасибо http://forums.pentaho.com/archive/index.php/t-87866.html?s=57295456a6292b611f3e46e3fa0ea7d6.

четверг, 22 августа 2013 г.

Oracle + Weblogic connection pool: задание схемы по-умолчанию

Недавно писал про организацию пула соединения с БД в приложении, развертываемом на Weblogic (http://barbitoff.blogspot.ru/2013/07/weblogic-1035-connection-pooling.html). Xml-конфигурация пула позволяет задать все параметры соединения, кроме имени используемой схемы. Хардкодить имя схемы в SQL-запросах не хочется, как и изобретать велосипед с подтягиванием имени схемы из какого-то своего отдельного конфига. Выход - использовать в SQL схему по-умолчанию (т.е. не задавать имя схемы вообще) и воспользоваться таким параметром пула, как "init-sql". В этом параметре нужно указать SQL-запрос, который при инициализации соединения с БД поменяет схему по-умолчанию на необходимую нам. Этот SQL зависит от используемой СУБД, для Oracle он выглядит так:
  <jdbc-connection-pool-params>
    <max-capacity>20</max-capacity>
    <connection-reserve-timeout-seconds>25</connection-reserve-timeout-seconds>
    <test-table-name>SQL SELECT 1 FROM DUAL</test-table-name>
    <init-sql>SQL ALTER SESSION SET CURRENT_SCHEMA = USR</init-sql>  </jdbc-connection-pool-params>

суббота, 6 июля 2013 г.

Weblogic 10.3.5: connection pooling в веб-приложении

Когда-то давно писал об организации connection pool'а в веб-приложении, разворачиваемом на Tomcat 6, на этот же раз возникла необходимость в аналогичном решении для Weblogic 10.3.5.
Ниже описан случай, когда WAR-ник деплоится в составе EAR-ника.
Во-первых, необходимо создать в EAR-нике xml-файл с конфигурацией пула. Поместить его в принципе можно где угодно, я разместил в META-INF/datasources/ и назвал mydb-jdbc.xml. Содержимое у него примерно следующее (на примере Oracle):
<jdbc-data-source xsi:schemaLocation="http://www.bea.com/ns/weblogic/90/domain.xsd"
 xmlns="http://xmlns.oracle.com/weblogic/jdbc-data-source"
 xmlns:sec="http://www.bea.com/ns/weblogic/90/security"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:wls="http://www.bea.com/ns/weblogic/90/security/wls">
  <name>mydb-datasource</name>
  <jdbc-driver-params>
    <url>jdbc:oracle:thin:@//db_host:db_port/db_servicename</url>
    <driver-name>oracle.jdbc.OracleDriver</driver-name>
    <properties>
      <property>
        <name>user</name>
        <value>*****</value>
      </property>
      <property>
        <name>password</name>
        <value>*****</value>
      </property>
    </properties>
  </jdbc-driver-params>
  <jdbc-connection-pool-params>
    <max-capacity>20</max-capacity>
    <connection-reserve-timeout-seconds>25</connection-reserve-timeout-seconds>
    <test-table-name>SQL SELECT 1 FROM DUAL</test-table-name>
  </jdbc-connection-pool-params>
</jdbc-data-source>
Тут в целом все достаточно очевидно. 
После создания данного файла нужно добавить ссылку на него в дескриптор приложения (META-INF/weblogic-application.xml в EAR-нике):
<?xml version="1.0" encoding="UTF-8"?>
<weblogic-application xmlns="http://xmlns.oracle.com/weblogic/weblogic-application" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/javaee_5.xsd http://xmlns.oracle.com/weblogic/weblogic-application http://xmlns.oracle.com/weblogic/weblogic-application/1.0/weblogic-application.xsd">
  <module>
    <name>mydb-datasource-module</name>
    <type>JDBC</type>
    <path>META-INF/datasources/mydb-jdbc.xml</path>
  </module>
</weblogic-application>
Теперь осталось лишь в web.xml WAR-ника, входящего в EAR, добавить ссылку на соотв. ресурс:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <!-... -->
  <resource-ref>
    <res-ref-name>mydb-datasource</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
  </resource-ref>
</web-app>
Все, теперь в коде WAR-ника коннекты к БД получаются так:
    InitialContext ctx = new InitialContext();
    javax.sql.DataSource ds =
            (javax.sql.DataSource) ctx.lookup("java:comp/env/mydb-datasource");
    conn = ds.getConnection();


суббота, 17 ноября 2012 г.

Java: p6spy или JDBC-logging anywhere

Иногда возникает необходимость трэйсить SQL-запросы к БД в Java-приложении. Если приложение свое (или чужое, но есть доступ к исходникам и возможность их пересобрать) - проблема решается просто:
DriverManager.setLogWriter(writer);
Connection con = DriverManager.getConnection(...);
...
или
DataSource.setLogWriter(writer);
Если доступа к исходникам нет - тут уже все становится сложнее. Предположим, что у нас есть хотя бы доступ к некоторому properties-файлу, в котором задаются параметры JDBC-соединения изучаемого приложения: драйвер, URL и т.п., например:
# The database driver
database.driver = transbase.jdbc.Driver
# The database server or connection string
database.connectionString = jdbc:transbase://localhost:2024/mydb
# Database Login and Password
database.user = iamadmin
...
В таком случае возникает естественная идея - нельзя ли вместо настоящего драйвера подложить какой-нибудь свой, который будет трэйсить запросы и затем отправлять их уже настоящему драйверу БД. Сделать это действительно можно, да и прокси-драйвер такой уже написан. Более того, он является бесплатным (распространяется по Apache Software License). Называется этот драйвер p6spy, доступен для скачки здесь: http://sourceforge.net/projects/p6spy/.
Вот пример подключения p6spy к веб-приложению, развернутому на Tomcat 6:
  1. Качаем с sourceforge файл p6spy-install.jar, распаковываем 
  2. Кладем p6spy.jar в /webapps/<appname>/WEB-INF/lib 
  3. Кладем spy.properties в /webapps/<appname>/WEB-INF/classes
  4. Меняем название драйвера в properties-файле приложения на "com.p6spy.engine.spy.P6SpyDriver" (все остальные параметры подключения не трогаем)
  5. Редактируем spy.properties:
    logfile = D:\spy.log
    excludecategories=
    realdriver=transbase.jdbc.Driver
    Здесь мы задали файл, куда будут писаться логи (я на всякий случай его заранее создал), убрали все исключения из логирования, чтобы в логи писалось абсолютно все, что можно, а также задали имя настоящего драйвера.
  6. Готово, перезагружаем веб-приложение (или томкат целиком), читаем логи =)

суббота, 20 октября 2012 г.

"Invalid operation on closed cursor" при просмотре данных в таблице БД Transbase из Netbeans

Попытался посмотреть БД Transbase, используя из JDBC-драйвер и Netbeans. Подключился без проблем, список таблиц получил, а вот SELECT`ы не выполняются, валится ошибка:
Код ошибки 30004, положение SQL : TB Java Client error <30004>: Invalid operation on closed cursor
Строка 1, столбец 1
То ли дело в кривом драйвере, то ли в Netbeans`е. Пришлось искать другой способ полазить по БД, и я наткнулся на вполне годную программку JDBC Navigator: http://thomasokken.com/jdbcnav/. Она показывает данные таблиц без проблем, используя тот же драйвер.

среда, 11 апреля 2012 г.

c3p0: com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector -- APPARENT DEADLOCK!!!

Проблема:

При нагрузочном тестировании веб-приложения, использующего пул соединений c3p0, при высокой нагрузке (порядка 170 запросов в секунду) иногда в логи валится исключение следующего вида:
com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector -- APPARENT DEADLOCK!!!
приводящие, в конечном итоге, к SQLExceptio`у в приложении.
Конфигурация c3p0 следующая:
<Resource name="jdbc/myDb" auth="Container"
  type="com.mchange.v2.c3p0.ComboPooledDataSource"
  factory="org.apache.naming.factory.BeanFactory"
  user="xxx" password="yyy" driverClass="oracle.jdbc.driver.OracleDriver"
  jdbcUrl="jdbc:oracle:thin:@//myoraserver:1521/zzz"
  maxPoolSize="50"
  maxIdleTime="7200"
  minPoolSize="5"
  testConnectionOnCheckout="true"
  idleConnectionTestPeriod="300"
  maxIdleTimeExcessConnections="120"/>  

Решение:

Здесь в одном из постов предполагается, что такая проблема вызвана одновременным закрытием закэшированного PreparedStatement и используемого им соединения (как такое может случится, я не понял, но пост писал человек, видимо, видевший исходники c3p0, так что я поверил ему на слово), и рекомендуется два выхода:
  1. Запретить кэширование  PreparedStatement`ов вообще установкой maxStatements=0
  2. Установить maxStatements в 0, а maxStatementsPerConnection равным (или чуть большим) чем число различных  PreparedStatement`ов, используемых приложением, чтобы уменьшить вероятность возникновения данной ситуации. 
Почему 2 вариант поможет, я не совсем понял, но попробовал его у себя, т.к. помимо борьбы с вышеуказанной проблемой, такая настройка позволяет повысить эффективность кэширования (негативно, правда, сказываясь на использовании памяти) за счет того, что все используемые приложением  PreparedStatement `ы будут браться из кэша. Не помогло - исключения всё равно валятся, так что пришлось прибегнуть к первому варианту. Но и он оказался безуспешным - валились те же исключения.
После долгих поисков был найден следующий вариант решения этой проблемы: если установить numHelperThreads равным maxPoolSize, APPARENT DEADLOCK`и проходят. Не самый красивый вариант в плане требовательности к ресурсам, но работает.
Второй вариант, на котором я, пожалуй, и остановлюсь - это установка maxAdministrativeTaskTime в некоторое достаточно большое значение, за которое предполагается, что любой запрос к БД выполнится (например, 600 сек). Тогда пока это время не истечет, c3p0 не будет расценивать ситуацию как deadlock. Документация утверждает, что установка этого значения в 0 приводит к тому, что "зависший" поток никогда не будет прерван, однако реально это нет так: c3p0 достаточно быстро ругается на deadlock, а через 60 секунд вызывает interrupt() потока.

c3p0: "WARNING: A C3P0Registry mbean is already registered" при переразвертывании веб-приложения на Tomcat

Проблема:
При повторном развертывании веб-приложения, использующего пул JDBC-соединений c3p0 как JNDI DataSource в консоль вываливается сообщение:
WARNING: A C3P0Registry mbean is already registered. This probably means that an application using c3p0 was undeployed, but not all PooledDataSources were closed prior to undeployment. This may lead to resource leaks over time. Please take care to close all PooledDataSources.
Решение:
PooledDataSource необходимо закрывать перед отменой развертывания приложения. Для этого в ServletContextListener`е в методе contextDestroyed() нужно выполнить следующий код:
import com.mchange.v2.c3p0.DataSources;
import com.mchange.v2.c3p0.PooledDataSource;

try
  {
  Context initContext = new InitialContext();
  DataSource ds = (DataSource)initContext.lookup("java:/comp/env/jdbc/myDataSource");
  if(ds instanceof PooledDataSource)
{
((PooledDataSource)ds).close();
DataSources.destroy(ds);
}
  }
catch(NamingException ex)
  {
  log.log(Level.SEVERE, "Got NamingException while cleaning up DataSource", ex);
  }
catch(SQLException ex)
  {
  log.log(Level.SEVERE, "Got NamingException while cleaning up DataSource with com.mchange.v2.c3p0.DataSources.destroy()", ex);
  } 

понедельник, 9 апреля 2012 г.

Замена пулу commons-dbcp в Tomcat 7

В Tomcat 7 на смену пулу JDBC-соединений commons-dbcp пришел tomcat-jdbc-pool, который вроде бы избавлен от недостатков commons-dbcp. Скачать новый пул можно отсюда: http://people.apache.org/~fhanik/jdbc-pool/. Использовать новый пул можно и на Tomcat 6, положив скачанный jar-ник в /lib томката и использовав следующую конфигурацию JNDI DataSource:

<Resource name="jdbc/myDb" auth="Container" type="javax.sql.DataSource"
  factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
  maxActive="200" maxIdle="30" maxWait="10000"
  username="xxx" password="yyy" driverClassName="oracle.jdbc.driver.OracleDriver"
  url="jdbc:oracle:thin:@//myora:1521/zzz"/>  

ЗЫ По некоторым данным, по производительности tomcat-jdbc-pool выигрывает не только у старичка DBCP, но и у c3po. По результатам моего же тестирования с конфигурацией, указанной выше, результаты примерно следующие (при нагрузке 5 запросов / сек; абсолютные значения не имеют значения т.к. приложение много чего ещё делает помимо взаимодействия с БД, важна разница между значениями):
  • commons-dbcp : 85-90 мс
  • tomcat-jdbc-pool: 110-115 мс
  • c3p0: 105-110 мс
  • oracle.jdbc.pool.OracleConnectionPoolDataSource: 115-120 мс
ЗЫЗЫ после небольшого докручивания c3p0 показал результат, аналогичный commons-dbcp.

вторник, 20 марта 2012 г.

Инициализация InitialContext`а и stubbing пула JDBC-соединений при unit-тестировании веб-приложения за пределами контейнера

Проблема:

В приложении используются объекты, получаемые из InitialContext`а, которые помещаются туда контейнером сервлетов. Конкретно, используется пул соединений с БД, инициализируемый в context.xml веб-приложения для Tomcat следующим образом:
<Context antiJARLocking="true" path="/SMEVMonitoring">
  <Resource name="jdbc/myDb" auth="Container" type="javax.sql.DataSource"
               maxActive="100" maxIdle="1" maxWait="10000"
               username="postgres" password="postgres" driverClassName="org.postgresql.Driver"
               url="jdbc:postgresql://postgreshost:5432/myDb"/>
</Context>
Используется пул следующим образом:
Context initContext = new InitialContext();
Context envContext  = (Context)initContext.lookup("java:/comp/env");
DataSource dataSource = (DataSource)envContext.lookup("jdbc/myDb");
jdbcConn = dataSource.getConnection();
При проведении unit-теста в IDE, естественно, по адресу "java:/comp/env/jdbc/myDb", ничего не оказывается, т.к. DataSource создается Tomcat`ом. Более того, оказывается неустановленным системное свойство "java.naming.factory.initial", поэтому контекст вообще не может инициализироваться.

Решение:

Перед запуском теста (например, в методе, помеченном @BeforeClass в JUnit) необходимо выполнить настройку InitialContext`а и положить в него необходимые объекты (в моем случае - DataSource, который, вместо пула, я заменил BasicDataSource`ом):
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.postgresql.Driver");
ds.setUrl("jdbc:postgresql://postgreshost:5432/myDb");
ds.setUsername("postgres");
ds.setPassword("postgres");
// Configure and populate initial context
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES,        "org.apache.naming");    
Context initContext = new InitialContext();
initContext.createSubcontext("java:");
initContext.createSubcontext("java:/comp");
initContext.createSubcontext("java:/comp/env");
initContext.createSubcontext("java:/comp/env/jdbc");
initContext.bind("java:/comp/env/jdbc/myDb",ds);
Для компиляции и выполнения тестов потребуется, чтобы к проекту были подключены библиотеки Tomcat`а (т.к. используется org.apache.naming.java.javaURLContextFactory).

четверг, 2 февраля 2012 г.

Быстрый старт: использование пула соединений с БД в веб-приложении, разворачиваемом на Tomcat 6

Одной из возможностей, предоставляемых контейнером сервлетов является использование пула соединений с БД, позволяющего более эффективно взаимодействовать с ней благодаря возможности повторного использования уже открытых соединений вместо создания новых.
Неплохое HOWTO по этой теме есть в документации Tomcat, ниже я приведу короткую выдержку оттуда для случая создания пула соединений к Postgres и его использования в сервлете.
1) Описываем JNDI-ресурс в context.xml веб-приложения (META-INF/context.xml, подробнее про элемент Context можно почитать тут), разместив внутри тега <Context> следующее:
<Resource name="jdbc/myDb"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="postgres"
password="postgres"
driverClassName="org.postgresql.Driver"
url="jdbc:postgresql://localhost:5432/myDB"/>

Здесь "jdbc/myDb" - это имя ресурса, по которому он будет доступен из приложения.
2) Описываем ссылку на ресурс в web.xml приложения:
<resource-ref>
<description>It`s myDb DataBase</description>
<res-ref-name>jdbc/myDb</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>

3) Теперь в коде сервлета мы можем получить соединение следующим образом:

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.sql.Connection;
// ...
Context initContext = new InitialContext();
Context envContext  = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/myDb");
Connection jdbcConn = ds.getConnection();
4) Завершив работу с соединением, его можно закрыть, как обычно:
jdbcConn.close();
jdbcConn = null;
При этом запись null`а позволит Вам случайно не закрыть это соединение повторно где-нибудь в блоке finally, что очень опасно при использовании пула соединений: если первый вызов close() всего лишь сигнализирует томкату, что приложение больше не нуждается в соединении и он может отдать его другому потоку, то повторный вызов действительно закроет соединение, которое на момент этого вызова может быть уже отдано другому потоку!

суббота, 24 сентября 2011 г.

Установка кодировки JDBC-ODBC

Установка настройки соединения для JDBC-ODBC моста осуществляется через Properties при создании соединения:
java.util.Properties connInfo = new java.util.Properties();
connInfo.put("charSet", "Cp1251");
connInfo.put("user", login);
connInfo.put("password", pass);
conn = DriverManager.getConnection(URL,connInfo);
Установленная таким образом кодировка повлияет на все символьные данные, получаемые / передаваемые с помощью этого соединения (в частности на то, какую кодировку будет использовать метод ResultSet.getString() при формировании строки).

пятница, 23 сентября 2011 г.

Transbase ODBC Driver и ошибка при извлечении NUMERIC данных с помощью JDBC-ODBC моста методом getObject

Проблема: 
При попытке извлечь данные из ячейки столбца, имеющего тип "NUMERIC" с помощью метода getObject объекта ResultSet (или просто при попытке просмотра данных в таблице БД Transbase с помощью вкладки "Services" в Netbeans, который видимо тоже при извлечении данных пользуется методом ResultSet.getObject) вылетает исключение вида:

java.sql.SQLException: [Transaction][ODBC Transbase Driver][TEST_DB] unknown: FieldId=5 ???

В логах ODBC-драйвера трансбэйза та же ошибка, однако причина её всё равно не очевидна:


[ODBC Transbase Driver][E:\utmp\seibt\tbodbc3x\trunk\api\sqlcolattribute.c][SQLColAttribute][419]unknown: FieldId=5 ???
[ODBC Transbase Driver][E:\utmp\seibt\tbodbc3x\trunk\api\sqlcolattribute.c][SQLColAttribute][431]Stmt=$0075F200 / Col=27 / FieldId=5 [SQL_DESC_???] <- 0 (len:0 / max: 0)

Решение:
Если тип столбца - "NUMERIC", для извлечения данных использовать метод getFloat вместо универсального getObject (в примере ниже resMD - объект типа ResultSetMetaData, полученный вызовом sourceDataRS.getMetaData(), а sourceDataRS - собственно ResultSet, из которого получаются данные):

 if(resMD.getColumnType(i)==Types.NUMERIC)
row[i] = sourceDataRS.getFloat(i);
else
row[i] = sourceDataRS.getObject(i);