Do you remember this?? (for those who have worked before with DBCP : validationQuery)
I was tired to look for this attribute in Websphere and I found it :P
Tip: On DB2 the equivalent to SELECT 1 FROM DUAL is SELECT 1 FROM SYSIBM.SYSDUMMY1
Thursday, September 30, 2010
Monday, September 20, 2010
Pass Collection of Objects with Attributes to a Stored Procedure using iBatis
In my last entry I had a requirement which needed to pass a List to an Oracle Stored Procedure, well, that thing is history.
But now the problem becomes more complex, because, what happen if I'm expecting on my Stored Proc and Type (STRUCT)
This is my Collection in Oracle!
And this is my Stored Proc (this is just a sample which receives the TABLE of Records)
This is the table where I'm going to INSERT
Obviously, the solution is Create a Handler like I did on my last entry, but is not going to be as simple, because I need to Map my Oracle STRUCT with my Java Code, so I have to do an domain object (POJO) coupled to Oracle and my Handler.
Once we have our Mapper class, then let's do the Handler!!!
But now the problem becomes more complex, because, what happen if I'm expecting on my Stored Proc and Type (STRUCT)
This is my Collection in Oracle!
And this is my Stored Proc (this is just a sample which receives the TABLE of Records)
This is the table where I'm going to INSERT
Obviously, the solution is Create a Handler like I did on my last entry, but is not going to be as simple, because I need to Map my Oracle STRUCT with my Java Code, so I have to do an domain object (POJO) coupled to Oracle and my Handler.
Once we have our Mapper class, then let's do the Handler!!!
Friday, September 17, 2010
Pass Collection to Stored Procedure using iBatis
Today I had a really crazy idea: "Why not making my query work with a list of parameters like I do with Hibernate using the operator IN???"
It sounds simple, Isn't it??? Ok, then try to do it with an Stored Procedure in Oracle :P
Then, this is my Stored Proc (I had to create a Type to have support for Arrays in Oracle, because an Array is not a simple-provided data type):
CREATE OR REPLACE TYPE NUMBER_ARRAY AS TABLE OF NUMBER;
CREATE OR REPLACE PROCEDURE retrieve_mtn_eligibility_state (
p_cust_id IN NUMBER,
p_acct_num IN NUMBER,
p_array_mtn IN NUMBER_ARRAY,
p_recordset OUT SYS_REFCURSOR
) AS
BEGIN
OPEN p_recordset FOR
SELECT mtn_eligibility_state, mtn_eligibility_state_date, mtn, acct_num, cust_id
FROM CUST_ACCT_MTN_STATE
WHERE cust_id = p_cust_id
AND acct_num = p_acct_num
AND mtn IN (
SELECT * FROM TABLE(p_array_mtn)
);
--TODO: Verify MTN given has CUST_ACCT_MTN_ORDER.bdy_mtn
dbms_output.put_line('This is the content of that array');
FOR i IN 1 .. p_array_mtn.count LOOP
dbms_output.put_line(i||'=' || p_array_mtn(i));
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Some other kind of error occurred.');
END;
/
As you can see, my dear visitor, I'm expecting some simple parameters and I'm returning a ResultSet, really straight forward, but the problem comes with the Array of NUMBER (that's why I created my
The solution to this problem is easier than it seems, just create a Handler dude!!!
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor;
import com.ibm.ws.rsadapter.jdbc.WSJdbcConnection;
import com.ibm.ws.rsadapter.jdbc.WSJdbcUtil;
import com.ibatis.sqlmap.client.extensions.ParameterSetter;
import com.ibatis.sqlmap.client.extensions.ResultGetter;
import com.ibatis.sqlmap.client.extensions.TypeHandlerCallback;
public class NumberArrayTypeHandlerCallBack implements TypeHandlerCallback {
@SuppressWarnings("unchecked")
public void setParameter(ParameterSetter setter, Object parameter) throws SQLException {
if(parameter == null) {
setter.setNull(Types.ARRAY);
}
else if(parameter instanceof ArrayList) {
Statement stmt = setter.getPreparedStatement();
Connection nativeConnection = (Connection) WSJdbcUtil.getNativeConnection((WSJdbcConnection) stmt.getConnection());
ArrayDescriptor desc = ArrayDescriptor.createDescriptor("NUMBER_ARRAY", nativeConnection);
parameter = new ARRAY(desc, nativeConnection, ((ArrayList) parameter).toArray());
}
setter.setObject(parameter);
}
public Object getResult(ResultGetter getter) throws SQLException {
return null;
}
public Object valueOf(String arg0) {
return null;
}
}
The magic begins when I transform the Connection (because I have to work at the connection level) provided by the Application Server (WebSphere on this case - If this happens to me on another App Server or with another Connection Pool, I'm going to be in trouble, because the issue will be how to retrieve a Native java.sql.Connection, but that will be another story).
Let's analyze this monster:
* Get Native Connection
Connection nativeConnection = (Connection) WSJdbcUtil.getNativeConnection((WSJdbcConnection) stmt.getConnection());
*Create my ArrayDescriptor for my Type (these NUMBER_ARRAY is my TYPE I created previously)
ArrayDescriptor desc = ArrayDescriptor.createDescriptor("NUMBER_ARRAY", nativeConnection);
*Create my parameter, an ARRAY (al fin y al cabo) and we're set!
parameter = new ARRAY(desc, nativeConnection, ((ArrayList) parameter).toArray());
Finally this is my mapping file for my stored procedure
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="SP">
<parameterMap id="objectParameters" class="java.util.Map">
<parameter property="cust_id" jdbcType="INTEGER" javaType="java.lang.String" mode="IN" />
<parameter property="acc_num" jdbcType="INTEGER" javaType="java.lang.String" mode="IN" />
<parameter property="array_mtn" jdbcType="NUMBER_ARRAY" typeName="NUMBER_ARRAY" typeHandler="mx.com.company.common.db.typehandler.NumberArrayTypeHandlerCallBack" mode="IN"/>
<parameter property="result" javaType="java.sql.ResultSet" jdbcType="ORACLECURSOR" mode="OUT"/>
</parameterMap>
<resultMap id="resultSetMap" class="mx.com.company.MyDomainObject" >
<result column="mtn_eligibility_state" property="mtnEligibilityState" />
<result column="mtn_eligibility_state_date" property="mtnEligibilityStateDate" />
</resultMap>
<procedure id="retrieve_mtn_eligibility_state" parameterMap="objectParameters" resultMap="resultSetMap" >
{call ${env.qualifier}.retrieve_mtn_eligibility_state(?,?,?,?)}
</procedure>
</sqlMap>
And my code lived happily ever after!!! Abur!!!
It sounds simple, Isn't it??? Ok, then try to do it with an Stored Procedure in Oracle :P
Then, this is my Stored Proc (I had to create a Type to have support for Arrays in Oracle, because an Array is not a simple-provided data type):
TYPE NUMBER_ARRAY AS TABLE OF NUMBER)
The solution to this problem is easier than it seems, just create a Handler dude!!!
Let's analyze this monster:
* Get Native Connection
*Create my ArrayDescriptor for my Type (these NUMBER_ARRAY is my TYPE I created previously)
*Create my parameter, an ARRAY (al fin y al cabo) and we're set!
Finally this is my mapping file for my stored procedure
And my code lived happily ever after!!! Abur!!!
Tags:
Array,
Collection,
iBatis,
Java,
List,
Oracle,
Stored Procedure
Friday, September 10, 2010
JDBC url for oracle RAC
If you have to connect to oracle RAC (Real Application Cluster) using JDBC with thin driver, the classic url:
Instead, you must use this url:
If you have an oracle client, like toad, check the tsnames.ora for the correct values of SERVICE_NAME and host.
jdbc:oracle:thin:@<HOST>:1521:<SID>doesn’t work and you get the error ORA – 12505.
Instead, you must use this url:
jdbc:oracle:thin:@(DESCRIPTION=(LOAD_BALANCE=on)
(ADDRESS=(PROTOCOL=TCP)(HOST=host1) (PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=host2) (PORT=1521))
(CONNECT_DATA=(SERVICE_NAME=service)))
Wednesday, September 8, 2010
Log SQL Statements on Hibernate
Hibernate, in the end, is all about dispatching SQL statements. SQL is at the heart of communication with an RDBMS system, and it is extremely important when struggling through performance problems or other bugs, that you know what is going on. After all, knowing the executed SQL allows you to determine the number of queries to complete an O/R mapping task, not to mention that seeing SQL queries is critical to understanding how to optimize the queries via indices and other database settings.
There are two ways (due to the legacy of Hibernate) to enable SQL logging. The first is to simply enable SQL logging in the Hibernate configuration By setting the hibernate.show_sql to TRUE
This is a nice quick and dirty solution, but it is relatively inflexible. SQL statements are always logged to System.out when that property is enabled, and on some servers, System.out isn't accessible by the average developer; or is cluttered with a million other log statements.
Alternatively, Hibernate uses the Apache-Jakarta Commons Logging package, which means that it will utilize log4j , java.util.logging , or potentially another logging framework. Typically, log messages are sent to commons logging, and then they are dispatched to one of these logging frameworks. Then, those logging frameworks determine where the message should be sent (log files, sockets, emails, database records, system out, system err, etc). It is easy to see how using these log frameworks will increase the flexibility of log statements. With Hibernate, simply setting the org.hibernate.SQL logger to 'DEBUG' or 'ALL' will ensure that all SQL statements are logged to the console.
If you set up a log4j category for org.hibernate.type
Get it to write out to the same log file as the org.hibernate.SQL
The type one will list the parameters for you after the SQL e.g.
There are two ways (due to the legacy of Hibernate) to enable SQL logging. The first is to simply enable SQL logging in the Hibernate configuration By setting the hibernate.show_sql to TRUE
This is a nice quick and dirty solution, but it is relatively inflexible. SQL statements are always logged to System.out when that property is enabled, and on some servers, System.out isn't accessible by the average developer; or is cluttered with a million other log statements.
Alternatively, Hibernate uses the Apache-Jakarta Commons Logging package, which means that it will utilize log4j , java.util.logging , or potentially another logging framework. Typically, log messages are sent to commons logging, and then they are dispatched to one of these logging frameworks. Then, those logging frameworks determine where the message should be sent (log files, sockets, emails, database records, system out, system err, etc). It is easy to see how using these log frameworks will increase the flexibility of log statements. With Hibernate, simply setting the org.hibernate.SQL logger to 'DEBUG' or 'ALL' will ensure that all SQL statements are logged to the console.
If you set up a log4j category for org.hibernate.type
Get it to write out to the same log file as the org.hibernate.SQL
The type one will list the parameters for you after the SQL e.g.
2006-07-28 09:57:12,061 DEBUG org.hibernate.SQL - insert into BASKET_LINE_ALLOC (LAST_UPDATED, QUANTITY, CUSTOMER_REF, NOTES, BRANCH_ID, FUND_ID, TEMPLATE_ID, BASKET_LINE_ALLOC_ID) values (?, ?, ?, ?, ?, ?, ?, ?) 2006-07-28 09:57:12,081 DEBUG org.hibernate.type.TimestampType - binding '2006-07-28 09:57:12' to parameter: 1 2006-07-28 09:57:12,081 DEBUG org.hibernate.type.IntegerType - binding '3' to parameter: 2 2006-07-28 09:57:12,082 DEBUG org.hibernate.type.StringType - binding '' to parameter: 3 2006-07-28 09:57:12,082 DEBUG org.hibernate.type.StringType - binding '' to parameter: 4 2006-07-28 09:57:12,082 DEBUG org.hibernate.type.LongType - binding '511' to parameter: 5 2006-07-28 09:57:12,082 DEBUG org.hibernate.type.LongType - binding '512' to parameter: 6 2006-07-28 09:57:12,082 DEBUG org.hibernate.type.LongType - binding null to parameter: 7 2006-07-28 09:57:12,082 DEBUG org.hibernate.type.LongType - binding '180030' to parameter: 8
@Autowired and @Qualifier
"Your best friends could be @Autowired and @Qualifier"
I have to migrate data from DB2 to Oracle, both databases have the same structure, well, my Oracle has better design, but I'm reusing the some objects, like POJO's and DAO's, the only thing I changed were my mappings, because I have to keep my mappings for DB2 and for Oracle.
If I have the same DAO for both databases, what can I do?
daoApplicationContext.xml
classpath:com/vzw/dvs/mapping/mappingDB2.cfg.xml
org.hibernate.dialect.DB2Dialect
false
true
true
true
false
false
15
true
org.hibernate.hql.ast.ASTQueryTranslatorFactory
4
classpath:com/vzw/dvs/mapping/mappingOracle.cfg.xml
org.hibernate.dialect.Oracle10gDialect
false
true
true
true
false
false
15
true
org.hibernate.hql.ast.ASTQueryTranslatorFactory
4
As you can see, I have 2 different DAO's sharing the same Interface & Implementation, but each one belongs to a different "sessionFactory"
serviceApplicationContext.xml
Nothing special, right? But now here it comes the interesting part, because @Autowired by itself it will inject the given bean in your ServiceImpl, but you must have only one bean of that type. In my case I have more than 1, so @Autowired will throw an error when loading the context.
Then it comes @Qualifier which will match based on the given name, and problem solved chavos!!!
public class MigrateTablesServiceImpl implements MigrateTablesService {
private static final Logger log = Logger.getLogger(MigrateTablesServiceImpl.class);
@Autowired
@Qualifier("UserDAOdb2")
private UserDAO userDAOdb2;
@Autowired
@Qualifier("UserDAO")
private UserDAO userDAO;
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public boolean doMigrateUser() {
log.debug("Migrating User");
boolean result = false;
try{
List listItems = this.userDAOdb2.getAll();
for(User transientInstance: listItems){
log.debug("Item retrieved from DB2 "+transientInstance);
User user = new User();
BeanUtils.copyProperties(transientInstance, user);
//Trim Strings
user.setDbUserId(StringUtils.trim(transientInstance.getDbUserId()));
user.setBudgetmanagerind(StringUtils.trim(transientInstance.getBudgetmanagerind()));
user.setDashboardaccess(StringUtils.trim(transientInstance.getDashboardaccess()));
user.setDirectorusercd(StringUtils.trim(transientInstance.getDirectorusercd()));
user.setEmail(StringUtils.trim(transientInstance.getEmail()));
user.setFirstname(StringUtils.trim(transientInstance.getFirstname()));
user.setFunctionalareacd(StringUtils.trim(transientInstance.getFunctionalareacd()));
user.setHomephone(StringUtils.trim(transientInstance.getHomephone()));
user.setInternalit(StringUtils.trim(transientInstance.getInternalit()));
user.setLastname(StringUtils.trim(transientInstance.getLastname()));
user.setMobilephone(StringUtils.trim(transientInstance.getMobilephone()));
user.setPassword(StringUtils.trim(transientInstance.getPassword()));
user.setUserid(StringUtils.trim(transientInstance.getUserid()));
user.setUserstatuscd(StringUtils.trim(transientInstance.getUserstatuscd()));
user.setVpid(StringUtils.trim(transientInstance.getVpid()));
user.setUtilizationtarget(StringUtils.trim(transientInstance.getUtilizationtarget()));
user.setWorkphone(StringUtils.trim(transientInstance.getWorkphone()));
if(user.getDbTimestamp()==null){
user.setDbTimestamp(new Date());
}
this.userDAO.save(user);
}
result = true;
}catch (Exception ex) {
log.warn(ex);
}
log.debug("Done");
return result;
}
Notes: I used org.springframework.beans.BeanUtils to create a copy of my bean (pojo) returned from DB2 into a non-persistent Pojo which I'll manipulate before inserting into Oracle (DB2 completes each column value with spaces to achieve the given size of that column, so each time you query a DB2 column you'll get a lot of extra spaces)
I have to migrate data from DB2 to Oracle, both databases have the same structure, well, my Oracle has better design, but I'm reusing the some objects, like POJO's and DAO's, the only thing I changed were my mappings, because I have to keep my mappings for DB2 and for Oracle.
If I have the same DAO for both databases, what can I do?
daoApplicationContext.xml
As you can see, I have 2 different DAO's sharing the same Interface & Implementation, but each one belongs to a different "sessionFactory"
Nothing special, right? But now here it comes the interesting part, because @Autowired by itself it will inject the given bean in your ServiceImpl, but you must have only one bean of that type. In my case I have more than 1, so @Autowired will throw an error when loading the context.
Then it comes @Qualifier which will match based on the given name, and problem solved chavos!!!
Notes: I used org.springframework.beans.BeanUtils to create a copy of my bean (pojo) returned from DB2 into a non-persistent Pojo which I'll manipulate before inserting into Oracle (DB2 completes each column value with spaces to achieve the given size of that column, so each time you query a DB2 column you'll get a lot of extra spaces)
Wednesday, September 1, 2010
EmbeddedId in Hibernate, AnnotatedClasses
I had to do some inserts to a table with a composite PK:
Nothing special, I decided to use Hibernate with annotations (but what about the PK: just create another class -duh, but How can I tell hibernate this is my PK? @EmbeddedId and @Embeddable):
@Embeddable
public class RimMessageId implements Serializable {
private static final long serialVersionUID = -1L;//Just 4 fun ;)
@Column(name = "dvs_server", length = 10, nullable = false)
private String server;
@Column(name = "dvs_yyyy", length = 4, nullable = false)
private String year;
@Column(name = "dvs_month", length = 2, nullable = false)
private String month;
@Column(name = "dvs_dd", length = 2, nullable = false)
private String day;
@Column(name = "dvs_hh", length = 2, nullable = false)
private String hour;
@Column(name = "dvs_mm", length = 2, nullable = false)
private String minute;
@Column(name = "dvs_ss", length = 2, nullable = false)
private String second;
@Column(name = "dvs_millis", length = 6, nullable = false)
private String millis;
//Gette's and Setter's
}
@Entity
@Table(name = "RIM_MESSAGES")
public class RimMessage implements Serializable {
private static final long serialVersionUID = 1L;;//Just 4 fun ;)
@EmbeddedId
private RimMessageId id;
@Column(name = "mdn", length = 10)
private String mdn;
@Column(name = "bun", length = 20)
private String bun;
@Column(name = "evt", length = 6)
private String evt;
@Column(name = "bsh", length = 2)
private String bsh;
@Column(name = "bex", length = 10)
private String bex;
@Column(name = "lng", length = 3)
private String lng;
@Column(name = "rim_tod", length = 4)
private String rimTod;
@Column(name = "rim_doy", length = 8)
private String rimDoy;
@Column(name = "rim_ip", length = 15)
private String rimIp;
//Gette's and Setter's
}
If you want to include in your sessionFactory (daoApplicationContext.xml) my annotated pojos, add this entry
As a reminder, if you want to use a propeties file to provide parameter for your DataSource, you have to do this:
classpath:rim-rtr-messages.properties
Nothing special, I decided to use Hibernate with annotations (but what about the PK: just create another class -duh, but How can I tell hibernate this is my PK? @EmbeddedId and @Embeddable):
@EmbeddableDefines my composite PK
@EmbeddedIdIncludes my composite Pk in my main Pojo
If you want to include in your sessionFactory (daoApplicationContext.xml) my annotated pojos, add this entry
As a reminder, if you want to use a propeties file to provide parameter for your DataSource, you have to do this:
Subscribe to:
Posts (Atom)