한개의 Table에서 2개의 Auto Timestamp 컬럼 사용하기(MySQL5.x)

다음은 MySQL5.0부터 추가된 트리거를 이용한 2개이상의 timestamp필드를 업데이트 하는 방법을 기술한다.

It’s very common to be in need of more than one auto-TIMESTAMP column in one table, firing on different operations: The blog you’re reading right now for example stores the time an article has first been published (corresponds to an INSERT operation) as well as the time an article has last been updated (corresponds to an UPDATE operation).

What you possibly want to do to achieve this is something like the following:

 CREATE TABLE blog_entries (
 `published` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 `updated` TIMESTAMP DEFAULT ‘0000-00-00 00:00:00’ ON UPDATE CURRENT_TIMESTAMP,
 `title` VARCHAR(128)
 );

But this still won’t work in 5.0. The server returns an error:
ERROR 1293 (HY000): Incorrect table definition; there can be only one
TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause

Instead we have to take a different approach and skip the built-in functionality of TIMESTAMP columns completely. Let’s define the table first:

 CREATE TABLE blog_entries (
  `published` TIMESTAMP DEFAULT ‘0000-00-00 00:00:00’,
  `updated` TIMESTAMP DEFAULT ‘0000-00-00 00:00:00’,
  `title` VARCHAR(128)
 );

We now have a table with two TIMESTAMP columns that both almost act as ordinary DATETIME columns, which means they are not set automatically. Let’s add the intended functionality for the published column again using a TRIGGER that fires every time just before a new row is inserted into the table:

 CREATE TRIGGER blog_entries_publish BEFORE INSERT ON `blog_entries`
 FOR EACH ROW SET NEW.published = NOW(), NEW.updated = ‘0000-00-00 00:00:00’;

In a BEFORE TRIGGER you can access and change the value to be inserted/updated via the NEW keyword. That’s what we did with this TRIGGER: Whenever a new row will be added to the table blog_entries our TRIGGER fires and sets the value for the column published to the actual time at the moment of the INSERT and the column updated to its default value of a zero timestamp (meaning in our example that this row has never been updated).
Let’s do almost the same for the UPDATE operation, just setting updated to the current time here and keeping the OLD value (the one being stored in the table before the UPDATE) for the column published:

 CREATE TRIGGER blog_entries_update BEFORE UPDATE ON `blog_entries`
 FOR EACH ROW SET NEW.updated = NOW(), NEW.published = OLD.published;

You’ll notice however that this is a very strict version of auto-TIMESTAMP columns: Our TRIGGERs override whatever values we try to INSERT or UPDATE for the two columns. This means it’s actually no longer possible to set these two columns manually.
In some situations this might be just what you want: For auditing, for example. You can now be sure that nobody ever messes around with your TIMESTAMPs (at least nobody with no rights to DROP your TRIGGERs). But if you want to be less strict with your users you can still build something that more closely emulates the built-in behaviour of TIMESTAMP columns:

 CREATE TABLE blog_entries (
  `published` TIMESTAMP NULL DEFAULT NULL,
  `updated` TIMESTAMP NULL DEFAULT NULL,
  `title` VARCHAR(128)
 );

CREATE TRIGGER blog_entries_publish BEFORE INSERT ON `blog_entries` FOR EACH ROW   SET
  NEW.published = IFNULL(NEW.published, NOW()),
  NEW.updated = IFNULL(NEW.updated, ‘0000-00-00 00:00:00’);

CREATE TRIGGER blog_entries_update BEFORE UPDATE ON `blog_entries` FOR EACH ROW SET
  NEW.updated = IF(NEW.updated = OLD.updated OR NEW.updated IS NULL, NOW(), NEW.updated),
  NEW.published = IFNULL(NEW.published, OLD.published);

The magic functionality of our columns is now only triggered if a NULL value is written to them. By setting the two columns to DEFAULT NULL we make sure this is the case if you just leave them out in your INSERT statements.
For the UPDATE operation it’s not as easy: If you don’t provide a value for the updated column its NEW value will be the same as its OLD and not the default of NULL during execution of the TRIGGER. So you’ll have to check for this situation as well, making it impossible to explicitly set the value of the updated column to the same as it was before with the above code. But you could of course change it and define NULL to mean “same as before” for the updated column:

 CREATE TRIGGER blog_entries_update BEFORE UPDATE ON `blog_entries` FOR EACH ROW SET
  NEW.updated = CASE
    WHEN NEW.updated IS NULL THEN OLD.updated
    WHEN NEW.updated = OLD.updated THEN NOW()
   ELSE NEW.updated
 END,
  NEW.published = IFNULL(NEW.published, OLD.published);

That’s the nice thing with TRIGGERs: Just taylor it to your needs!


출처 : http://www.futhark.ch/mysql/108.html 

Table용 Page Util

package kr.or.struts.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>Title: PageUtil </p>
 * <p>Description: Table Page Util </p>
 * <p>Copyright: Copyright (c) 2007 Maxoft.Inc</p>
 * <p>Company: Maxoft.Inc </p>
 * @author Kim sooki
 * @version 1.0
 */
public class PageUtil {
 // 객체없이 사용하는 utitlity클래스
 private PageUtil(){
 }
 
 private static Log logger = LogFactory.getLog(PageUtil.class);
 
// 글의 갯수만큼 입력받아 모든페이지의 링크를 생성
// String page : page이름
// int total : 전체 글 갯수
// int showLine : 한페이지에 표시될 글의 갯수
// int current : 현재페이지
 public static String getAllPageLink(String page,int total, int showLine,int current){
  if(total == 0)
   return “”;
  StringBuffer buffer = new StringBuffer();  
  int count = 1;  
  for(int i = 1; i <= total; i=i+showLine){
   buffer.append(“<a href=”);
   buffer.append(page);
   buffer.append(“PageNo=”);  
   buffer.append(Integer.toString(count));
   buffer.append(“>”);
   if(count == current){
     buffer.append(“<strong>”);
    buffer.append(Integer.toString(count));
     buffer.append(“</strong>”);  
   } else

   buffer.append(Integer.toString(count));
   buffer.append(“</A>&nbsp;&nbsp;”);  
   count++;
  }
  return buffer.toString();
 }
 
 // 전체 글수와 현재페이지수를 입력하여 전체 페이지수를 구함
 public static int getMaxPage(int total, int showLine){
  if(total <= 0){
   return 0;
  }
  int temp = total / showLine;
  int mode = total % showLine;
  if(mode != 0){
   temp++;
  }
  return temp;
 }
 
 // 페이지에서 시작되는 가장 나중번호를 구한다.
 public static int getRowStart(int total, int showLine,int currentPage){
  if(total <= showLine){
   return total;
  }
  int temp = showLine * (currentPage-1);
  temp = total – temp;
  if(temp < 0){
   temp = 0;
  }
  return temp;
 }
 // 페이지에서 시작되는 가장 적은 번호를 구한다.
 public static int getRowEnd(int total, int showLine,int currentPage){
  if(total <= showLine){
   return 0;
  }
  int temp = (showLine * (currentPage -1)) + showLine;
  temp = total – temp;
  if(temp < 0 ){
   temp = 0;
  } 
  return temp;
 }
}

Property Facade

Property Facade를 작성하여 여러 Property설정파일을 읽어들여  세팅하도록 해봤다.


public class PropertiesFacade
{


    public PropertiesFacade()
    {
    }


    public void loadProperties(String fileName)
    {
        if(properties == null)
            try
            {
                String localFilePath = “resources/config/”;
                String jarFilePath = “/config/”;
                URL url = getClass().getResource(jarFilePath + fileName);
                InputStream fileIn;
                if(url == null)
                    fileIn = new FileInputStream(localFilePath + fileName);
                else
                if(url.getFile().toString().indexOf(“!”) != -1)
                {
                    JarURLConnection jarConn = (JarURLConnection)url.openConnection();
                    fileIn = jarConn.getInputStream();
                } else
                {
                    fileIn = new FileInputStream(url.getPath());
                }
                properties = new Properties();
                properties.load(fileIn);
                fileIn.close();
            }
            catch(Exception e)
            {


                e.printStackTrace();
               


            }
    }


    public Properties getProperties()
    {
        return properties;
    }


    public String getProperty(String name)
    {
        if(properties.getProperty(name) != null)
            return properties.getProperty(name);
        else
            return “null”;
    }



    private Properties properties;


}


  getProperties()나 getProperty(name)을 이용하여 설정된 프로퍼티를 가져올수 있다.



 A class extends PropertiesFacade 처럼 상속하여 추가기능으로 사용하도록 만들었다..

String localFilePath = “resources/config/”;
String jarFilePath = “/config/”;
의 path를 적절히 바꾸어 사용하면 된다.

Key Mapped DBCP







 해쉬코드를 키값으로 하는 DBConnection Pool을 한번 만들어 보았다..



/**
 * <p>Title: ConnectionPool</p>
 * <p>Description: ConnectionPool</p>
 * <p>Copyright: Copyright (c) 2005 Maxoft.Inc</p>
 * <p>Company: Maxoft.Inc </p>
 * @author Kim sooki
 * @version 1.0
 */
package net.maxoft.center.controller;



import com.mysql.jdbc.Statement;
import java.io.*;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.*;
import net.maxoft.model.GlobalsFactory;
import org.apache.log4j.Logger;

public class ConnectionPool extends Thread {
 


  public static synchronized ConnectionPool getInstance(){
    if(instance == null){
      instance = new ConnectionPool();
    }
    return instance;
  }

 private ConnectionPool() {
    use = 0;
    close = false;
    available = 0;
   
    pool = new HashMap();
    keyMap = new HashMap();
   
    try{
      makeConnections();
    }catch(Exception e){
      e.printStackTrace();
      running = false;
      logger.debug(“Connection pool create fail!!”);
    }
    logger.info(“Connection Pool created!!”);  
 } 

 private void makeConnections() throws SQLException{
   
    for(int i = 0 ; i < ConnectionPool.MAX_ALIVE; i++){       
      try{
        Class.forName(GlobalsFactory.getGlobals().getProperty(“jdbcDriverClass”)).newInstance();
        String URL = GlobalsFactory.getGlobals().getProperty(“jdbcConnectionUrl”);
        Connection connection = DriverManager.getConnection(URL,”root”,”****”);
        int hash = connection.hashCode();
        pool.put(new Integer(hash), connection);
        keyMap.put(new Integer(hash), new Integer(hash));
        logger.debug(“Make connection  : “+ connection + ” / ” + hash + ”  / ” + available);
        available++;
       
      }catch(Exception e){
        e.printStackTrace();       
      }    
    }
   
    if( pool.size() == 0){
      throw new SQLException(“Create fail Connection!!!”);
    }
    logger.debug(“Connection : ” + available + ” created!”);
 }
 
  private Connection addConnection() throws SQLException {
    try{
      Class.forName(GlobalsFactory.getGlobals().getProperty(“jdbcDriverClass”)).newInstance();
      String URL = GlobalsFactory.getGlobals().getProperty(“jdbcConnectionUrl”);
      Connection connection = DriverManager.getConnection(URL,”root”,”****”);  
      logger.debug(connection);
      return connection;
     
    }catch(Exception e){
      e.printStackTrace();    
      throw new SQLException(“Connection create fail!”);
    }      
  }
 
  private void closeAllConnection(){
   
    Iterator iter = pool.keySet().iterator();
    while(iter.hasNext()){
      Integer hash = (Integer)iter.next();
      Connection con = null;
      try{
        con = (Connection)pool.remove(hash);
        con.close();
        available–;
       
      }catch(Exception e){
        e.printStackTrace();       
      }finally {
        if(con != null){
          try{
            con.close();
          }catch(Exception e){
          } // inner try – catch;
        }  // if(con != null)    
      } // outter try – catch;     
    } // while – iterator
    logger.debug(“Connection all closed!!”); 
  }
 
  protected void finalize() throws Throwable {
    if(close != false){
      close();     
    }
    super.finalize();
  } 



  public synchronized void close(){
    if(close == true)
      return;
    close = true;
    closeAllConnection();
    pool = null;
  }
 
  public synchronized void releaseConnection(Connection con) throws SQLException { 
    if(con == null){
      throw new SQLException(“Connection is null!!”);
    }
     
    synchronized(pool){
      int hash = con.hashCode();
      if(keyMap.containsKey(new Integer(hash))){
        pool.put(new Integer(hash), con);
        available++;
        use–;
      } else {
        try{
          con.close();
        }catch(Exception e){
          logger.debug(“Close connection error!”);
          e.printStackTrace();
          throw new SQLException(“Close connection error!”);
        }
      }
    }
  }
 
  public synchronized Connection getConnection() throws SQLException {       
     
    if(use > 50){
      throw new SQLException(“MAX Connection Alive!!”);
    }       
    synchronized(pool){
      if(pool.size() > 0){
        Iterator iter = pool.keySet().iterator();
        if(!iter.hasNext())
          logger.debug(“key not exist!!”);
         
        Integer hash = (Integer)iter.next();
        Connection con = (Connection)pool.remove(hash);
        available–;
        use++;       
        return con;
      }      
    }  
    return addConnection();   
  } 
 
 
  public void run(){
    // 시간마다 avaliable connection의 상태를 점검
    // 연결이 닫혔으면 map에서 삭제하고 다시 연결한뒤 저장
    // Connection에러가 발생하여 0개의 conn이 유효한경우 종료
    // running = false 인경우 종료
  }
 
 
  private Connection con; 
  private boolean running = true;
  private static final int MAX_ALIVE = 10;
  private int available;
  private int use;
  private HashMap pool;
  private HashMap keyMap; 
  private static ConnectionPool instance;
  private boolean close;
  private static final Logger logger;
   
  static
  {
      logger = Logger.getLogger(net.maxoft.center.controller.ConnectionPool.class);
  }
 


}

Connection Pool 자체를 Thread로 설정하여 일정주기동안 Managed connection의 validate를 check 하도록 추가해 주어야 한다.



MAX_ALIVE의 갯수를 넘어서는 Connection요청은 Connection을 생성후 반납시 close()하도록 되어 있다.



드라이버이름과 URL은 Pref를 구현한 클래스로부터 얻어오도록 되어 있으므로 Prefs를 직접구현하도록 한다.

XMLEncoder/Decoder 이용하기







java.beans 패키지에 포함된 XMLEncoder를 이용하면 자바객체를 xml데이터로 저장할 수 있다.



– Object를 xml로 저장 –


Object object;


XMLEncoder output = new XMLEncoder(new FileOutputStream(“c:\test.xml”));
output.writeObject(object);


output2.flush();


output.close();



– xml에서 객체 생성 –


try{
 input = new XMLDecoder(new FileInputStream(“c:\test.xml”));
     
} catch(Exception e){}
Object o = input.readObject();
input.close();

Axis 에서 Map사용하기







 Axis에서는 배열형태나 Vector형태는 기본적으로 전송을 지원하지만 Map같은 Collection형태는 지원해주지 않는다.


wsdl2java를 이용하면 Map에 대응하는 java파일을 얻을수 있다.


– service.wsdl –


..


<complexType name=”mapItem”>
    <sequence>
     <element name=”key” nillable=”true” type=”xsd:anyType”/>
     <element name=”value” nillable=”true” type=”xsd:anyType”/>
    </sequence>
   </complexType>
   <complexType name=”Map”>
    <sequence>
     <element maxOccurs=”unbounded” minOccurs=”0″ name=”item” type=”apachesoap:mapItem”/>
    </sequence>
   </complexType>



– wsdl2java 로 얻어진 MapItem.java –


/**
 * MapItem.java
 *
 * This file was auto-generated from WSDL
 * by the Apache Axis 1.3 Oct 05, 2005 (05:23:37 EDT) WSDL2Java emitter.
 */


package org.apache.xml.xml_soap;


public class MapItem  implements java.io.Serializable {
    private java.lang.Object key;


    private java.lang.Object value;


    public MapItem() {
    }


    public MapItem(
           java.lang.Object key,
           java.lang.Object value) {
           this.key = key;
           this.value = value;
    }



    /**
     * Gets the key value for this MapItem.
     *
     * @return key
     */
    public java.lang.Object getKey() {
        return key;
    }



    /**
     * Sets the key value for this MapItem.
     *
     * @param key
     */
    public void setKey(java.lang.Object key) {
        this.key = key;
    }



    /**
     * Gets the value value for this MapItem.
     *
     * @return value
     */
    public java.lang.Object getValue() {
        return value;
    }



    /**
     * Sets the value value for this MapItem.
     *
     * @param value
     */
    public void setValue(java.lang.Object value) {
        this.value = value;
    }



}


– deploy.wsdl에 추가된 beanMapping –


<beanMapping
        xmlns:ns=”http://xml.apache.org/xml-soap
        qname=”ns:mapItem”
        languageSpecificType=”java:org.apache.xml.xml_soap.MapItem” 
       ENCODINGStyle=”http://schemas.xmlsoap.org/soap/ENCODING/
      />


– wsdl에서 Map의 참조방법 –


<element name=”customFields” nillable=”true” type=”apachesoap:Map”/>

Vector는 Object의 배열로 처리할수 있는것처럼 Map은 key와 value의 Pair인 MapItem배열을 통해 전송할 수있다.

JXL라이브러리를 이용한 액셀 다루기


 jxl라이브러리를 이용하면 excel문서를 읽고쓰는것이가능하다.



자세한 내용은 http://www.andykhan.com/ 에서 확인해 볼수 있다..



이번프로젝트에서는 csv파일을 이용하기때문에 라이브러리의 모든기능은 사용하지 않지만, excel 읽어서 csv로 저장해보았다.



File file = new File(c:\text.xls”);


String ENCODING = “KSC5601”;
              boolean hide = false;
               Workbook w = Workbook.getWorkbook(file);
               FileOutputStream fos = new FileOutputStream(outFile);
                           
               OutputStreamWriter osw = new OutputStreamWriter(fos, ENCODING);
               BufferedWriter bw = new BufferedWriter(osw);               
               for (int sheet = 0; sheet < w.getNumberOfSheets(); sheet++)
               {
                  Sheet s = w.getSheet(sheet);
               
                  if (!(hide && s.getSettings().isHidden()))
                  {
                    Cell[] row = null;
                   
                    for (int i = 0 ; i < s.getRows() ; i++)
                    {
                      row = s.getRow(i);
                     
                      if (row.length > 0)
                      {
                        if (!(hide && row[0].isHidden()))
                        {
                          bw.write(row[0].getContents());
                          // Java 1.4 code to handle embedded commas
                          // bw.write(“”” + row[0].getContents().replaceAll(“””,””””) + “””);
                        }
                       
                        for (int j = 1; j < row.length; j++)
                        {
                          bw.write(‘,’);
                          if (!(hide && row[j].isHidden()))
                            {
                            bw.write(row[j].getContents());
                            // Java 1.4 code to handle embedded quotes
                            //  bw.write(“”” + row[j].getContents().replaceAll(“””,””””) + “””);
                          }
                        }
                      }
                      bw.newLine();
                    }
                  }
                }
                bw.flush();
                bw.close();
   
               fos.close();
               w.close();


– 참고 –


jxl.Demo , jxl.CSV