Java tutorial
package com.gmail.sretof.db.jdbc.processor; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.SQLXML; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.persistence.Transient; import org.apache.commons.dbutils.BeanProcessor; import org.springframework.util.ReflectionUtils; import com.google.common.base.CaseFormat; import com.google.common.collect.Lists; /** * <p> * <code>BeanProcessor</code> matches column names to bean property names and * converts <code>ResultSet</code> columns into objects for those bean * properties. Subclasses should override the methods in the processing chain to * customize behavior. * </p> * * <p> * This class is thread-safe. * </p> * * @see BeanProcessor * * @since DbUtils 1.1 */ public class CamelBeanProcessor extends BeanProcessor { /** * Special array value used by <code>mapColumnsToProperties</code> that * indicates there is no bean property that matches a column from a * <code>ResultSet</code>. */ protected static final int PROPERTY_NOT_FOUND = -1; /** * Set a bean's primitive properties to these defaults when SQL NULL is * returned. These are the same as the defaults that ResultSet get* methods * return in the event of a NULL column. */ private static final Map<Class<?>, Object> primitiveDefaults = new HashMap<Class<?>, Object>(); /** * ResultSet column to bean property name overrides. */ private final Map<String, String> columnToPropertyOverrides; static { primitiveDefaults.put(Integer.TYPE, Integer.valueOf(0)); primitiveDefaults.put(Short.TYPE, Short.valueOf((short) 0)); primitiveDefaults.put(Byte.TYPE, Byte.valueOf((byte) 0)); primitiveDefaults.put(Float.TYPE, Float.valueOf(0f)); primitiveDefaults.put(Double.TYPE, Double.valueOf(0d)); primitiveDefaults.put(Long.TYPE, Long.valueOf(0L)); primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE); primitiveDefaults.put(Character.TYPE, Character.valueOf((char) 0)); } /** * Constructor for BeanProcessor. */ public CamelBeanProcessor() { this(new HashMap<String, String>()); } /** * Constructor for BeanProcessor configured with column to property name * overrides. * * @param columnToPropertyOverrides * ResultSet column to bean property name overrides * @since 1.5 */ public CamelBeanProcessor(Map<String, String> columnToPropertyOverrides) { super(); if (columnToPropertyOverrides == null) { throw new IllegalArgumentException("columnToPropertyOverrides map cannot be null"); } this.columnToPropertyOverrides = columnToPropertyOverrides; } /** * Convert a <code>ResultSet</code> row into a JavaBean. This implementation * uses reflection and <code>BeanInfo</code> classes to match column names * to bean property names. Properties are matched to columns based on * several factors: <br/> * <ol> * <li> * The class has a writable property with the same name as a column. The * name comparison is case insensitive.</li> * * <li> * The column type can be converted to the property's set method parameter * type with a ResultSet.get* method. If the conversion fails (ie. the * property was an int and the column was a Timestamp) an SQLException is * thrown.</li> * </ol> * * <p> * Primitive bean properties are set to their defaults when SQL NULL is * returned from the <code>ResultSet</code>. Numeric fields are set to 0 and * booleans are set to false. Object bean properties are set to * <code>null</code> when SQL NULL is returned. This is the same behavior as * the <code>ResultSet</code> get* methods. * </p> * * @param <T> * The type of bean to create * @param rs * ResultSet that supplies the bean data * @param type * Class from which to create the bean instance * @throws SQLException * if a database access error occurs * @return the newly created bean */ public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException { PropertyDescriptor[] props = this.propertyDescriptors(type); ResultSetMetaData rsmd = rs.getMetaData(); int[] columnToProperty = this.mapColumnsToProperties(rsmd, props); return this.createBean(rs, type, props, columnToProperty); } /** * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans. * This implementation uses reflection and <code>BeanInfo</code> classes to * match column names to bean property names. Properties are matched to * columns based on several factors: <br/> * <ol> * <li> * The class has a writable property with the same name as a column. The * name comparison is case insensitive.</li> * * <li> * The column type can be converted to the property's set method parameter * type with a ResultSet.get* method. If the conversion fails (ie. the * property was an int and the column was a Timestamp) an SQLException is * thrown.</li> * </ol> * * <p> * Primitive bean properties are set to their defaults when SQL NULL is * returned from the <code>ResultSet</code>. Numeric fields are set to 0 and * booleans are set to false. Object bean properties are set to * <code>null</code> when SQL NULL is returned. This is the same behavior as * the <code>ResultSet</code> get* methods. * </p> * * @param <T> * The type of bean to create * @param rs * ResultSet that supplies the bean data * @param type * Class from which to create the bean instance * @throws SQLException * if a database access error occurs * @return the newly created List of beans */ public <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException { List<T> results = new ArrayList<T>(); if (!rs.next()) { return results; } PropertyDescriptor[] props = this.propertyDescriptors(type); ResultSetMetaData rsmd = rs.getMetaData(); int[] columnToProperty = this.mapColumnsToProperties(rsmd, props); do { results.add(this.createBean(rs, type, props, columnToProperty)); } while (rs.next()); return results; } /** * Creates a new object and initializes its fields from the ResultSet. * * @param <T> * The type of bean to create * @param rs * The result set. * @param type * The bean type (the return type of the object). * @param props * The property descriptors. * @param columnToProperty * The column indices in the result set. * @return An initialized object. * @throws SQLException * if a database error occurs. */ private <T> T createBean(ResultSet rs, Class<T> type, PropertyDescriptor[] props, int[] columnToProperty) throws SQLException { T bean = this.newInstance(type); for (int i = 1; i < columnToProperty.length; i++) { if (columnToProperty[i] == PROPERTY_NOT_FOUND) { continue; } PropertyDescriptor prop = props[columnToProperty[i]]; Class<?> propType = prop.getPropertyType(); Object value = this.processColumn(rs, i, propType); if (propType != null && value == null && propType.isPrimitive()) { value = primitiveDefaults.get(propType); } this.callSetter(bean, prop, value); } return bean; } /** * Calls the setter method on the target object for the given property. If * no setter method exists for the property, this method does nothing. * * @param target * The object to set the property on. * @param prop * The property to set. * @param value * The value to pass into the setter. * @throws SQLException * if an error occurs setting the property. */ private void callSetter(Object target, PropertyDescriptor prop, Object value) throws SQLException { Method setter = prop.getWriteMethod(); if (setter == null) { return; } Class<?>[] params = setter.getParameterTypes(); try { // convert types for some popular ones if (value instanceof java.util.Date) { final String targetType = params[0].getName(); if ("java.sql.Date".equals(targetType)) { value = new java.sql.Date(((java.util.Date) value).getTime()); } else if ("java.sql.Time".equals(targetType)) { value = new java.sql.Time(((java.util.Date) value).getTime()); } else if ("java.sql.Timestamp".equals(targetType)) { value = new java.sql.Timestamp(((java.util.Date) value).getTime()); } } // Don't call setter if the value object isn't the right type if (this.isCompatibleType(value, params[0])) { setter.invoke(target, new Object[] { value }); } else { throw new SQLException("Cannot set " + prop.getName() + ": incompatible types, cannot convert " + value.getClass().getName() + " to " + params[0].getName()); // value cannot be null here because isCompatibleType allows // null } } catch (IllegalArgumentException e) { throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage()); } catch (IllegalAccessException e) { throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage()); } catch (InvocationTargetException e) { throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage()); } } /** * ResultSet.getObject() returns an Integer object for an INT column. The * setter method for the property might take an Integer or a primitive int. * This method returns true if the value can be successfully passed into the * setter method. Remember, Method.invoke() handles the unwrapping of * Integer into an int. * * @param value * The value to be passed into the setter method. * @param type * The setter's parameter type (non-null) * @return boolean True if the value is compatible (null => true) */ private boolean isCompatibleType(Object value, Class<?> type) { // Do object check first, then primitives if (value == null || type.isInstance(value)) { return true; } else if (type.equals(Integer.TYPE) && Integer.class.isInstance(value)) { return true; } else if (type.equals(Long.TYPE) && Long.class.isInstance(value)) { return true; } else if (type.equals(Double.TYPE) && Double.class.isInstance(value)) { return true; } else if (type.equals(Float.TYPE) && Float.class.isInstance(value)) { return true; } else if (type.equals(Short.TYPE) && Short.class.isInstance(value)) { return true; } else if (type.equals(Byte.TYPE) && Byte.class.isInstance(value)) { return true; } else if (type.equals(Character.TYPE) && Character.class.isInstance(value)) { return true; } else if (type.equals(Boolean.TYPE) && Boolean.class.isInstance(value)) { return true; } return false; } /** * Factory method that returns a new instance of the given Class. This is * called at the start of the bean creation process and may be overridden to * provide custom behavior like returning a cached bean instance. * * @param <T> * The type of object to create * @param c * The Class to create an object from. * @return A newly created object of the Class. * @throws SQLException * if creation failed. */ protected <T> T newInstance(Class<T> c) throws SQLException { try { return c.newInstance(); } catch (InstantiationException e) { throw new SQLException("Cannot create " + c.getName() + ": " + e.getMessage()); } catch (IllegalAccessException e) { throw new SQLException("Cannot create " + c.getName() + ": " + e.getMessage()); } } /** * Returns a PropertyDescriptor[] for the given Class. * * @param c * The Class to retrieve PropertyDescriptors for. * @return A PropertyDescriptor[] describing the Class. * @throws SQLException * if introspection failed. */ private PropertyDescriptor[] propertyDescriptors(Class<?> c) throws SQLException { // Introspector caches BeanInfo classes for better performance BeanInfo beanInfo = null; try { beanInfo = Introspector.getBeanInfo(c); } catch (IntrospectionException e) { throw new SQLException("Bean introspection failed: " + e.getMessage()); } List<PropertyDescriptor> propList = Lists.newArrayList(); PropertyDescriptor[] props = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor prop : props) { String propName = prop.getName(); try { Field field = ReflectionUtils.findField(c, propName); if (field != null && !field.isAnnotationPresent(Transient.class)) {// 1.field=null // 2.Transient?? propList.add(prop); } } catch (SecurityException e) { throw new SQLException("Bean Get Field failed: " + e.getMessage()); } } return propList.toArray(new PropertyDescriptor[propList.size()]); } /** * The positions in the returned array represent column numbers. The values * stored at each position represent the index in the * <code>PropertyDescriptor[]</code> for the bean property that matches the * column name. If no bean property was found for a column, the position is * set to <code>PROPERTY_NOT_FOUND</code>. * * @param rsmd * The <code>ResultSetMetaData</code> containing column * information. * * @param props * The bean property descriptors. * * @throws SQLException * if a database access error occurs * * @return An int[] with column index to property index mappings. The 0th * element is meaningless because JDBC column indexing starts at 1. */ protected int[] mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor[] props) throws SQLException { int cols = rsmd.getColumnCount(); int[] columnToProperty = new int[cols + 1]; Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND); for (int col = 1; col <= cols; col++) { String columnName = rsmd.getColumnLabel(col); if (null == columnName || 0 == columnName.length()) { columnName = rsmd.getColumnName(col); } columnName = columnName.toLowerCase(); String propertyName = columnToPropertyOverrides.get(columnName); if (propertyName == null) { propertyName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, columnName);// ? } for (int i = 0; i < props.length; i++) { String prop = props[i].getName(); if (propertyName.equalsIgnoreCase(prop)) { columnToProperty[col] = i; break; } } } return columnToProperty; } /** * Convert a <code>ResultSet</code> column into an object. Simple * implementations could just call <code>rs.getObject(index)</code> while * more complex implementations could perform type manipulation to match the * column's type to the bean property type. * * <p> * This implementation calls the appropriate <code>ResultSet</code> getter * method for the given property type to perform the type conversion. If the * property type doesn't match one of the supported <code>ResultSet</code> * types, <code>getObject</code> is called. * </p> * * @param rs * The <code>ResultSet</code> currently being processed. It is * positioned on a valid row before being passed into this * method. * * @param index * The current column index being processed. * * @param propType * The bean property type that this column needs to be converted * into. * * @throws SQLException * if a database access error occurs * * @return The object from the <code>ResultSet</code> at the given column * index after optional type processing or <code>null</code> if the * column value was SQL NULL. */ protected Object processColumn(ResultSet rs, int index, Class<?> propType) throws SQLException { if (!propType.isPrimitive() && rs.getObject(index) == null) { return null; } if (propType.equals(String.class)) { return rs.getString(index); } else if (propType.equals(Integer.TYPE) || propType.equals(Integer.class)) { return Integer.valueOf(rs.getInt(index)); } else if (propType.equals(Boolean.TYPE) || propType.equals(Boolean.class)) { return Boolean.valueOf(rs.getBoolean(index)); } else if (propType.equals(Long.TYPE) || propType.equals(Long.class)) { return Long.valueOf(rs.getLong(index)); } else if (propType.equals(Double.TYPE) || propType.equals(Double.class)) { return Double.valueOf(rs.getDouble(index)); } else if (propType.equals(Float.TYPE) || propType.equals(Float.class)) { return Float.valueOf(rs.getFloat(index)); } else if (propType.equals(Short.TYPE) || propType.equals(Short.class)) { return Short.valueOf(rs.getShort(index)); } else if (propType.equals(Byte.TYPE) || propType.equals(Byte.class)) { return Byte.valueOf(rs.getByte(index)); } else if (propType.equals(Timestamp.class)) { return rs.getTimestamp(index); } else if (propType.equals(SQLXML.class)) { return rs.getSQLXML(index); } else { return rs.getObject(index); } } }