I have used JDBC to connect to many different relational systems over the years: H2, HSQLDB, MySQL, Oracle, Postgres, etc. And in every case, each system seems to have its own flavor of connection string syntax.
I can't imagine that a long-standing API like JDBC wouldn't have a defined, enforced grammar for connection string validation. For example:
Some valid HSQLDB connection strings:
jdbc:hsqldb:mem:mymemdb
jdbc:hsqldb:res:org.my.path.resdb
jdbc:hsqldb:file:/opt/db/testdb
MySQL:
jdbc:mysql://localhost/test
Postgres:
jdbc:postgresql://localhost/test
H2:
jdbc:h2:~/test
jdbc:h2:file:/data/sample
jdbc:h2:tcp://dbserv:8084/~/sample
From all these examples, I gather the basic, generalized syntax:
jdbc:<vendor>:<vendor-specific-uri>
Where <vendor> is the name of the system (h2, mysql, etc.), and <vendor-specific-uri> is either a path or some vendor-specific way of determining the location of a database.
I've done a lot of digging, and for the life of me, I can't seem to find where JDBC defines valid connection string syntax. Specifically:
What is the general grammar/definition of a valid JDBC connection string?
What are the different names of each token/component of the connection string? For instance, is "jdbc:" called something, like the "JDBC protocol"? What is the proper name for my <vendor> and <vendor-specific-uri> segments?
The URL syntax is specified in the JDBC specification, specifically in section 9.4:
The format of a JDBC URL is :
jdbc:<subprotocol>:<subname>
where subprotocol defines the kind of database connectivity mechanism that may be supported by one or more drivers. The contents and syntax of the subname will depend on the
subprotocol.
Note – A JDBC URL is not required to fully adhere to the URI syntax as defined in RFC 3986, Uniform Resource Identifier (URI): Generic Syntax
There is no more formality to it than this. The subprotocol usually is some identifier for the database or the driver. Subname is freeform, although drivers usually do follow URI-like syntax.
There is also no need for more formality. The DriverManager will simply offer the URL to each registered java.sql.Driver in turn and the first one to accept is used to connect.
If you take a look into the source code, you will see that a JDBC driver implements java.sql.Driver. This interface has a method
boolean acceptsURL(String url) throws SQLException;
From the JavaDoc:
Retrieves whether the driver thinks that it can open a connection to the given URL. Typically drivers will return true if they understand the subprotocolspecified in the URL and false if they do not.
So the dirver for your database is responsible to implement this method. For H2, this implementation is
#Override
public boolean acceptsURL(String url) {
if (url != null) {
if (url.startsWith(Constants.START_URL)) {
return true;
} else if (url.equals(DEFAULT_URL)) {
return DEFAULT_CONNECTION.get() != null;
}
}
return false;
}
Other DBMS have different implementations.
Edit: For H2, the constant Constants.START_URL is "jdbc:h2:". So even the leading jdbc is not part of any formal grammar.
Related
We have an Oracle database with the following charset settings
SELECT parameter, value FROM nls_database_parameters WHERE parameter like 'NLS%CHARACTERSET'
NLS_NCHAR_CHARACTERSET: AL16UTF16
NLS_CHARACTERSET: WE8ISO8859P15
In this database we have a table with a CLOB field, which has a record that starts with the following string, stored obviously in ISO-8859-15: X²ARB (here correctly converted to unicode, in particular that 2-superscript is important and correct).
Then we have the following trivial piece of code to get the value out, which is supposed to automatically convert the charset to unicode via globalization support in Oracle:
private static final String STATEMENT = "SELECT data FROM datatable d WHERE d.id=2562456";
public static void main(String[] args) throws Exception {
Class.forName("oracle.jdbc.driver.OracleDriver");
try (Connection conn = DriverManager.getConnection(DB_URL);
ResultSet rs = conn.createStatement().executeQuery(STATEMENT))
{
if (rs.next()) {
System.out.println(rs.getString(1).substring(0, 5));
}
}
}
Running the code prints:
with ojdbc8.jar and orai18n.jar: X�ARB -- incorrect
with ojdbc7.jar and orai18n.jar: X�ARB -- incorrect
with ojdbc-6.jar: X²ARB -- correct
By using UNISTR and changing the statement to SELECT UNISTR(data) FROM datatable d WHERE d.id=2562456 I can bring ojdbc7.jar and ojdbc8.jar to return the correct value, but this would require an unknown number of changes to the code as this is probably not the only place where the problem occurs.
Is there anything I can do to the client or server configurations to make all queries return correctly encoded values without statement modifications?
It definitely looks like a bug in the JDBC thin driver (I assume you're using thin). It could be related to LOB prefetch where the CLOB's length, character set id and the first part of the LOB data is sent inband. This feature was introduced in 11.2. As a workaround, you can disable lob prefetch by setting the connection property
oracle.jdbc.defaultLobPrefetchSize
to "-1". Meanwhile I'll follow up on this bug to make sure that it gets fixed.
Please have a look at Database JDBC Developer's Guide - Globalization Support
The basic Java Archive (JAR) file ojdbc7.jar, contains all the
necessary classes to provide complete globalization support for:
CHAR or VARCHAR data members of object and collection for the character sets US7ASCII, WE8DEC, WE8ISO8859P1, WE8MSWIN1252, and UTF8.
To use any other character sets in CHAR or VARCHAR data members of
objects or collections, you must include orai18n.jar in the CLASSPATH
environment variable:
ORACLE_HOME/jlib/orai18n.jar
I have added multiple jars(ojdbc6 for oracle and jtds-1.2.jar for sqlserver ) to my classpath.
When I test connection using following code :
Class.forName(JDBC_DRIVER);
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
if(conn==null){
System.out.println("false");
}
else{
System.out.println("true");
}
Scenario 1:
If I give JDBC_DRIVER as sqlserver URL (net.sourceforge.jtds.jdbc.Driver)
and DB_URL,USER,PASS for my oracle instance then also it will create connection for me but logically it is wrong.
Scenario 2:
If I give some other(not oracle.jdbc.driver.OracleDriver ) valid class in ojdbc6.jar and valid DB_URL,USER,PASS then also it will create connection .
But I always want to check valid JDBC_DRIVER which corresponds to the given DB_URL,USER,PASS
I have also tried registerDriver and deregisterDriver API available in driverManager. What are the pros and cons of using it.
Explanation for - Scenario 1/Scenario 2
From oracle documentation
Applications no longer need to explictly load JDBC drivers using Class.forName(). Existing programs which currently load JDBC drivers using Class.forName() will continue to work without modification.
So even you comment out Class.forName(JDBC_DRIVER); you will get the same result as ojdbc6 is in your classpath and in DriverManager.getConnection(...); method you are passing oracle database related information.So DriverManager always will return the connection instance for oracle daabase only.
Explanation for- want to check valid JDBC_DRIVER which corresponds to the given DB_URL,USER,PASS
For this you can use getDriver(String url) method of DriverManager class.
getDriver(String url)
Attempts to locate a driver that understands the given URL.
To check valid driver name try these lines below.
conn = DriverManager.getConnection(this.dbUrl, this.dbUser, this.dbPassword);
DatabaseMetaData dbMetaData = conn.getMetaData();
this.databaseProperties.add("Driver Name: " + dbMetaData.getDriverName());
My connection string looks like this
String cn = "jdbc:odbc:DSN";
it works fine . However, when i try to modify it to
String cn = "jdbc:odbc:DSN, TYPE=FASTLOAD";
it does not establish connection
I also tried
String cn = "jdbc:odbc:DSN, TYPE=FASTLOADCSV";
Teradata's JDBC driver supports the FastLoad protocol, but you're not using it. You try to connect via JDBC-ODBC bridge, change to jdbc:teradata://...
Try
String cn = "jdbc:odbc:DSN; TYPE=FASTLOAD";
If you want to connect with ODBC, then use semicolons. But if you want to use FastLoad, then you need to connect using JDBC, in which case you should use commas and the forward slash like so:
String cn = "jdbc:teradata://servername/TYPE=FASTLOADCSV";
Also, you'll need to disable auto-committing whenever you fastload (at least if you do batch inserting, which you probably should). Fastload requires an empty table; committing causes the table to be non-empty. To prevent that issue, simply set autocommit to False before inserting, and set it back to True (or whatever you want it to be) after all inserts have been executed and committed.
Alternatively, you can pursue a different approach: commit stuff, but use staging tables. With this method you create new, empty tables for each insert batch. In the end you can consolidate those tables into one with the MERGE operation. If you do this process right, you can avoid any rewriting of data on disk. (Source: this other SO question)
More information:
Teradata JDBC tips on improving performance
Sample code from Teradata about JDBC, with boilerplate code on connectivity, CRUD, etc.
More Teradata docs on connectivity with JDBC
From within a java code - where I already have a connection to a database - I need to find the default schema of the connection.
I have the following code that gives me a list of all schemas of that connection.
rs = transactionManager.getDataSource().getConnection().getMetaData().getSchemas();
while (rs.next()) {
log.debug("The schema is {} and the catalogue is {} ", rs.getString(1), rs.getString(2));
}
However, I don't want the list of all the schemas. I need the default schema of this connection.
Please help.
Note1: I am using H2 and DB2 on Windows7 (dev box) and Linux Redhat (production box)
Note2: I finally concluded that it was not possible to use the Connections object in Java to find the default schema of both H2 and DB2 using the same code. I fixed the problem with a configuration file. However, if someone can share a solution, I could go back and refactor the code.
Please use connection.getMetaData().getURL() method which returns String like
jdbc:mysql://localhost:3306/?autoReconnect=true&useUnicode=true&characterEncoding=utf8
We can parse it easily and get the schema name. It works for all JDBC drivers.
I am working on a functionality where i need to check whether database is down or not. i have to use single query which works for both oracle and sqlserver dbs. is there any single query which checks whether db is up or not?
similar to select * from dual;
Thanks!
I'd go with a connection.setAutoCommit(true) and not with a select.
I think it would be best to use the Connection's function to check the server is available.
There should be an Exception when it fails to connect and you can check the state to see if it's still open.
To address your specific need, if you decide to continue with a query.
THIS IS NOT BEST PRACTICE
I don't know of a simple way to do what you wish, Oracle and SQL don't share the same naming for system objects. BUT run that command, it won't work on SQL, but the exception won't be of type 'Server is Down' and you can use it in your try/catch.
THIS IS NOT BEST PRACTICE
Hope it makes sense.
Better way is to obtain the connection and then use the database metadata information like the product version or product name to ensure the database is up or not.
Eg:
try{
Con = DriverManager.getConnection(databaseURL,username,password);
databasemetadata = con.getMetaData();
String databaseName = databasemetedata.getDatabaseProductName();
If(databaseName.equals("<desireddabase>"))
{
//database up and running
}
}
catch(Exception e)
{
// error in connection ...
}
The java.sql.Connection has a method
isValid(timeOut)
Returns true if the connection has not been closed and is still valid. The driver shall submit a query on the connection or use some other mechanism that positively verifies the connection is still valid when this method is called.