2015年7月18日 星期六

PK是流水號、@Results、@Result、@ResultMap、@SelectProvider (Mybatis3.x 三)

※PK是流水號的問題

首先先創一張表
CREATE TABLE CHESS(
    CHESS_NO NUMBER(10),
    NAME VARCHAR(10),
    PRICE NUMBER(5),
    PRODUCT_DATE DATE,
    CONSTRAINT CHESS_PK PRIMARY KEY(CHESS_NO)
);
CREATE SEQUENCE CHESS_SEQUENCE
    INCREMENT BY 1
    START WITH 1
    NOMAXVALUE
    NOCYCLE
    CACHE 10;
COMMENT ON TABLE CHESS IS '棋表';
COMMENT ON COLUMN CHESS.CHESS_NO IS '棋編號';
COMMENT ON COLUMN CHESS.NAME IS '棋名稱';
COMMENT ON COLUMN CHESS.PRICE IS '棋價錢';
COMMENT ON COLUMN CHESS.PRODUCT_DATE IS '棋的生產日期';

然後增加棋類,欄位對應好,然後給setter/getter,下面是Chess.xml
<insert id="insert" parameterType="Chess">
    INSERT INTO CHESS(
    <include refid="column" />
    )
    VALUES(CHESS_SEQUENCE.NEXTVAL, #{name}, #{price}, #{productDate})
</insert>

增加別名時,因為是同一個包,乾脆就增加package就可以了,mybatis會去這個包找,還要記得Chess.xml要設定
<typeAliases>
    <!-- <typeAlias type="org.mybatis.model.Dept" alias="Dept" /> -->
    <!-- <typeAlias type="org.mybatis.model.Chess" alias="Chess" /> -->
    <package name="org.mybatis.model"/>
</typeAliases>
<!--資料庫操作省略-->
<mappers>
    <mapper resource="org/mybatis/model/Dept.xml" />
    <mapper resource="org/mybatis/model/Chess.xml" />
</mappers>

※新增時,PK的問題

這時會發現新增時,PK居然是null
 System.out.println("----------Chess.insert----------");
 Chess chess = new Chess();
 chess.setName("五子棋");
 chess.setPrice(35);
 chess.setProductDate(new Date());
 int suc = sqlSession.insert("Chess.insert", chess);
 System.out.println("成功新增" + suc + "筆!");
 System.out.println("PK=" + chess.getChessNo());
 System.out.println("name=" + chess.getName());
 System.out.println("price=" + chess.getPrice());
 System.out.println("productDate=" + chess.getProductDate());

由於我用的是oracle,所以oracle的解法是這樣
<insert id="insert" parameterType="Chess">
    <!-- selectKey因為設定BEFORE,所以會在執行之前,會先執行裡面的語法到setChessNo裡 -->
    <selectKey keyProperty="chessNo" order="BEFORE" resultType="java.lang.Integer">
        SELECT CHESS_SEQUENCE.NEXTVAL FROM DUAL
    </selectKey>
    INSERT INTO CHESS(
    <include refid="column" />
    )
    以下兩行選擇其一,一般會用第二種
    <!--VALUES(CHESS_SEQUENCE.CURRVAL, #{name}, #{price}, #{productDate})-->
    VALUES(#{chessNo}, #{name}, #{price}, #{productDate}, #{clazz}, #{score}, #{school})
</insert>

※查詢時,欄位名稱的問題

新增一組PK查詢
<select id="getChessById" parameterType="java.lang.Integer" resultType="Chess" >
    SELECT
    <include refid="column" />
    FROM CHESS WHERE CHESS_NO = #{chessNo}
</select>

測試類如下,其實還要增加查不到是null的問題,才不會報Exception,測試就算了
System.out.println("----------Chess.getChessById----------");
Chess chess = sqlSession.selectOne("Chess.getChessById", 12);
System.out.println("chessNo=" + chess.getChessNo());
System.out.println("name=" + chess.getName());
System.out.println("price=" + chess.getPrice());
System.out.println("productDate=" + chess.getProductDate());
此時發現chessNo和productDate是null,因為資料庫有_,java沒有,他把他放到java那裡了,所以可以增加個別名,我最後面剛好是PRODUCT_DATE,所以直接加在後面
<select id="getChessById" parameterType="java.lang.Integer" resultType="Chess">
    SELECT
    <include refid="column" /> as productDate
    FROM CHESS WHERE CHESS_NO = #{chessNo}
</select>
但chessNo還是null,因為我沒加別名,但仔細想想,這個做法真是太爛了,一點都不好維護,所以mybatis提供了一個叫resultMap的東西,resultMap和resultType只能選擇其一,做法如下:
<resultMap type="Chess" id="ChessInterface">
    <result property="chessNo" column="CHESS_NO" />
    <result property="productDate" column="PRODUCT_DATE" />
</resultMap>

<select id="getChessById" parameterType="java.lang.Integer" resultMap="ChessInterface" >
    SELECT
    <include refid="column" />
    FROM CHESS WHERE CHESS_NO = #{chessNo}
</select>
type有別名的關係,可以直接寫,id隨便取
然後將我們剛剛的resultType改成resultMap,裡面放resultMap的id即可
我目前做的專案都是把全部的屬性打在裡面,像這樣:
<resultMap type="Chess" id="ChessInterface">
    <id property="chessNo" column="CHESS_NO" />
    <result property="name" column="NAME" />
    <result property="price" column="PRICE" />
    <result property="productDate" column="PRODUCT_DATE" />
</resultMap>
我把PK改成id標籤,結果是一樣的,不過既然是PK,最好就用id,我還試不出差在哪 剛剛第一種做法直接加在欄位後面是不分大小寫的,但property是有分的喔!

還可以用建構子設值,首先在 java bean 的類別裡用工具產生一個所有欄位的建構子,然後xml如下設定(注意順序問題):
<constructor>
    <idArg column="CHESS_NO" javaType="java.lang.Integer" />
    <arg column="NAME" javaType="java.lang.String" />
    <arg column="PRICE" javaType="java.lang.Integer" />
    <arg column="PRODUCT_DATE" javaType="java.util.Date" />
</constructor>


※jdbcType

預設是 OTHER,這在 Oracle 會不知道如何處理,可以改為 NULL 即可
譬如在新增時,欄位是可以 null 的,但 insert 時,還是會報 OTHER 不知道怎麼處理的錯誤,所以可以使用 #{fieldName, jdbcType=NULL}
但如果有很多這樣的東西,可以設定在全域設定這個值,就是在設定資料庫帳密的地方,寫在 properties 同級的下面,如下:
<settings>
    <setting name="jdbcTypeForNull" value="NULL" />
</settings>

※大小寫要注意



※@Results、@Result、@ResultMap

@Mapper
public interface DeptMapper2 {
    @Results(id = "d2", value = { 
        @Result(id = true, column = "deptno", property = "dno"),
//      @Result(column = "dname", property = "name"), 
        @Result(column = "loc", property = "loc") })
    @Select("select * from dept where deptno = #{deptNo}")
    public Dept2 getDeptById(int id);
    
    @ResultMap("d2")
    @Select("select * from dept")
    public List<Dept2> getAllDept2();
}

※使用 @Results 時,不能像 XML一樣單獨定義,其他的方法可以用 @ResultMap 使用,但本身有 @Results 不能再用 @ResultMap

※@Results 寫的地方有差,以此例來說,寫在getAllDept2 上面,那 getDeptById 就算用 @ResultMap 也抓不到




※@SelectProvider

@ResultMap("d2")
@SelectProvider(type = MyProvider.class, method="noParam")
public List<Dept2> providerTest();
    
@ResultMap("d2")
@SelectProvider(type = MyProvider.class, method="param")
public Dept2 providerTest2(@Param("xxx") int id);
    
    
    
public class MyProvider {
    public String noParam() {
        return "select * from dept";
    }
    
    public static String param(Map<String, Integer> map) {
        return "select * from dept where deptno = " + map.get("xxx");
    }
}

※使用 @SelectProvider 時,可以用 static,但不能使用 overloading

※有參數時,必須用 Map 接,Key 固定給 String,Value 和資料庫的型態對應好即可,如果怕出錯可給 Object

※@InsertProvider、@DeleteProvider、@UpdateProvider 也差不多

2015年7月17日 星期五

別名與CRUD和MybatisUtil Mybatis3.x(二)

※別名

上個例子的Dept.xml裡面的resultType,有可能有很多支都回傳一樣的物件,所以可以用別名的方式來對應,據說2.x是在Dept.xml裡設裡,3.x方法是在mybatis-config.xml裡設定,集中起來感覺比較好找,可參考這裡如下:
<properties resource="jdbc.properties"></properties>
<typeAliases>
    <typeAlias type="org.mybatis.model.Dept" alias="Dept" />
    <package name="org.mybatis.model" />
</typeAliases>

注意如果有「The content of element type "configuration" must match
 "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,plugins?,enviro
 nments?,databaseIdProvider?,mappers?)".」這樣的錯,有可能是你沒有按照順序設定,如properties->settings->typeAliases…等
這時Dept.xml就只要寫resultType="Dept"就可以了(不一定要resultType才可以用)

也可以用註解,@Alias("xxx") 寫在 java bean class 的最上面,但要配合掃瞄包,也就是 package name,這別名可以在 xml 使用 

都設定不會有問題,都可以用,如果只有掃瞄包,不寫 @Alias,預設是類名稱,開頭大小寫都可以

※增刪改查

Dept.xml如下,注意#{}裡面是對應getter方法的field
而parameterType,為傳進去的參數類型; resultType為傳回的結果
雖然getDeptById,我寫int,但我傳Dept整個物件依然可以,不過為了好維護,大家都能看懂的方式,最好寫物件就傳物件,寫int就傳int
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Dept">
    <select id="getDeptById" parameterType="int" resultType="Dept">
        SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE DEPTNO = #{deptNo}
    </select>
    
    <select id="findAll" resultType="Dept">
        SELECT DEPTNO, DNAME, LOC FROM DEPT
    </select>
    
    <insert id="insert" parameterType="Dept">
        INSERT INTO DEPT(DEPTNO, DNAME, LOC)
        VALUES(#{deptNo},#{dName},#{loc})
    </insert>
    
    <update id="update" parameterType="Dept">
        UPDATE DEPT SET
             DNAME = #{dName}
            ,LOC = #{loc}
        WHERE
            DEPTNO = #{deptNo}
    </update>
    
    <delete id="delete" parameterType="Dept">
        DELETE FROM DEPT WHERE DEPTNO = #{deptNo}
    </delete>
</mapper>

測試類:注意參數要對應Dept.xml的parameterType
InputStream is = null;
SqlSession sqlSession = null;
try {
    is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder()
            .build(is);
    sqlSession = factory.openSession();

    // 新增
    // System.out.println("----------Dept.insert----------");
    // Dept deptIns = new Dept();
    // deptIns.setDeptNo(60);
    // deptIns.setdName("Sales");
    // deptIns.setLoc("zh_CN");
    // int suc = sqlSession.insert("Dept.insert", deptIns);
    // System.out.println("成功新增" + suc + "筆!");

    // 修改
    // System.out.println("----------Dept.update----------");
    // Dept deptUpd = new Dept();
    // deptUpd.setDeptNo(60);
    // deptUpd.setdName("Shopping");
    // deptUpd.setLoc("zh_CN");
    // int upd = sqlSession.update("Dept.update", deptUpd);
    // System.out.println("成功修改" + upd + "筆!");

    // 刪除
    // System.out.println("----------Dept.delete----------");
    // Dept deptDel = new Dept();
    // deptDel.setDeptNo(60);
    // int del = sqlSession.delete("Dept.delete", deptDel);
    // System.out.println("成功刪除" + del + "筆!");

    // 查詢一筆
    System.out.println("----------Dept.getDeptById----------");
    //Dept d = new Dept();
    //d.setDeptNo(90);
    Dept dept = sqlSession.selectOne("Dept.getDeptById", 90);
    System.out.println("deptNo=" + dept.getDeptNo());
    System.out.println("dName=" + dept.getdName());
    System.out.println("loc=" + dept.getLoc());

    // 查詢全部
    // System.out.println("----------Dept.findAll----------");
    // List<Dept> dept = sqlSession.selectList("Dept.findAll");
    // for (Dept deptAll : dept) {
    // System.out.println("deptNo=" + deptAll.getDeptNo());
    // System.out.println("dName=" + deptAll.getdName());
    // System.out.println("loc=" + deptAll.getLoc());
    // }

    sqlSession.commit();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    sqlSession.close();
    try {
        if (is != null) {
            is.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

※xml裡的變數

Dept.xml裡的DEPTNO, DNAME, LOC欄位重覆好幾次,可以統一用一個類似變數的東西控管,如下:
<sql id="column">
    DEPTNO, DNAME, LOC
</sql>

<select id="getDeptById" parameterType="int" resultType="Dept">
    SELECT
    <include refid="column" />
    FROM DEPT WHERE DEPTNO = #{deptNo}
</select>

<select id="findAll" resultType="Dept">
    SELECT
    <include refid="column" />
    FROM DEPT
</select>

<insert id="insert" parameterType="Dept">
    INSERT INTO DEPT(
    <include refid="column" />
    )
    VALUES(#{deptNo},#{dName},#{loc})
</insert>

<update id="update" parameterType="Dept">
    UPDATE DEPT SET
    DNAME = #{dName}
    ,LOC = #{loc}
    WHERE
    DEPTNO = #{deptNo}
</update>

<delete id="delete" parameterType="Dept">
    DELETE FROM DEPT WHERE DEPTNO
    = #{deptNo}
</delete>
用<sql>標籤當變數,用<include refid="">可取得

※MybatisUtil

和Hibernate一樣,每次都要呼叫SessionFactory太麻煩了,所以加上這個類,以下是高手提供的寫法,ThreadLocal(區域執行緒),目的是把變數存在目前的執行緒中, 讓每個執行中的執行緒都有一份, 而且彼此之間不會互相影響
package cn.mldn.util;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MyBatisSessionFactory {
    private static final String CONFIG = "mybatis-config.xml";
    //ThreadLocal<SqlSession>讓SqlSession不會重覆被呼叫
    private static final ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>();
    private static SqlSessionFactory sessionFactory;
    private static Reader reader = null;

    static {
        try {
            reader = Resources.getResourceAsReader(CONFIG);
            sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private MyBatisSessionFactory() {
    }

    public static SqlSession getSession() {
        SqlSession session = (SqlSession) threadLocal.get();
        if (session == null) {
            if (sessionFactory == null) {
                rebuildSessionFactory();
            }
            session = (sessionFactory != null) ? sessionFactory.openSession()
                    : null;
            threadLocal.set(session);
        }
        return session;
    }

    public static void rebuildSessionFactory() {
        try {
            reader = Resources.getResourceAsReader(CONFIG);
            sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void closeSession() {
        SqlSession session = (SqlSession) threadLocal.get();
        threadLocal.set(null);
        if (session != null) {
            session.close();
        }
    }
    
    public static SqlSessionFactory getSessionFactory() {
        return sessionFactory;
    }
    
    public static Reader getConfiguration() {
        return reader;
    }
}

這樣子呼叫就把sqlSession = factory.openSession();改成sqlSession = MybatisUtil.getSession();
然後try catch拿掉,最後MybatisUtil.closeSession();即可,寫一下好了:
//本來是這樣
InputStream is = null;
SqlSession sqlSession = null;
try {
    is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder()
            .build(is);
    sqlSession = factory.openSession();
    //CRUD...
    sqlSession.commit();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    sqlSession.close();
    try {
        if (is != null) {
            is.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

//改成以下模樣
SqlSession sqlSession = MybatisUtil.getSession();
//CRUD...
sqlSession.commit();
MybatisUtil.closeSession();

※傳變數給xml

Map<String, Object> map = new HashMap<String, Object>();
//key要對應Dept.xml
map.put("column", "dname");//欄位名稱
map.put("keyWord", "%a%");//給like尋找的關鍵字
map.put("start", 2);// 從第幾筆
map.put("end", 4);// 到第幾筆

SqlSession session = MybatisUtil.getSession();
System.out.println("AllCount=" + session.selectOne("Dept.getAllCount", map));
List<Dept> all = session.selectList("Dept.findAllBySplit", map);
for(Dept d:all){
    System.out.print(d.getDeptNo() + "\t");
    System.out.print(d.getdName() + "\t");
    System.out.println(d.getLoc());
}
MybatisUtil.closeSession();

Demp.xml這樣設定,我用的是Oracle,所以就要用oracle的語法
<select id="findAllBySplit" parameterType="java.util.Map" resultType="Dept">
    SELECT <include refid="column" /> 
    FROM (
        SELECT ROWNUM AS RANK, <include refid="column" /> FROM DEPT ORDER BY DEPTNO
    )
    WHERE 
    LOWER(${column}) LIKE #{keyWord} AND
    RANK BETWEEN #{start} AND #{end}
</select>

<select id="getAllCount" parameterType="java.util.Map" resultType="java.lang.Integer">
    SELECT COUNT(deptno)
    FROM DEPT
    WHERE LOWER(${column}) LIKE #{keyWord}
</select>

2015年7月13日 星期一

Excel範例(POI)

※寫檔

public class TestExcel {
    /**
     * Excel左邊的數字,程式碼是從0開始的,不直覺,此方法傳進來的數字會轉換成和Excel一樣
     * 
     * @param leftDigit
     * @return
     */
    private static int viewRow(int leftDigit) {
        return leftDigit - 1;
    }

    /**
     * Excel上面的英文字,程式碼是從0開始的,不直覺,此方法傳進來的字元會轉換成和Excel一樣,只適用A~Z
     * 
     * @param topEngWord
     * @return
     */
    private static int viewCell(char topEngWord) {
        return topEngWord - 65;
    }

    public static void main(String[] args) {
        HSSFWorkbook excelbook = new HSSFWorkbook();
        HSSFSheet sheet = excelbook.createSheet("xxx");// 創建工作表

        // ※1A原始的字
        HSSFRow row1 = sheet.createRow(TestExcel.viewRow(1));
        HSSFCell cellA = row1.createCell(TestExcel.viewCell('A'));
        cellA.setCellValue("originalfasddddddddddddddd");

        // ※1B紅色的字
        HSSFCell cellB = row1.createCell(TestExcel.viewCell('B'));
        // 如果改成以下寫法,會變成後者蓋前者,所以1A會什麼都沒有
        // HSSFCell cellB =
        // sheet.createRow(TestExcel.viewRow(1)).createCell(TestExcel.viewCell('B'));
        cellB.setCellValue("我想紅");
        // 多下面的程式碼
        HSSFCellStyle cellFontStyle = excelbook.createCellStyle();
        Font font = excelbook.createFont();
        font.setColor(HSSFColor.RED.index);// 紅色
        font.setFontHeightInPoints((short) 20);// 字體大小
        // font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);// 粗體
        cellFontStyle.setFont(font);
        // cellFontStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);//左右對齊
        // cellFontStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_TOP);//上下對齊
        // cellFontStyle.setWrapText(true);// 自動換行
        cellB.setCellStyle(cellFontStyle);

        // ※1C背景色
        HSSFCell cellC = row1.createCell(TestExcel.viewCell('C'));
        cellC.setCellValue("綠背景");

        HSSFCellStyle cellBackgroundColor = excelbook.createCellStyle();
        cellBackgroundColor.setFillForegroundColor(HSSFColor.GREEN.index);
        cellBackgroundColor.setFillPattern((short) 1);
        cellC.setCellStyle(cellBackgroundColor);

        // 欄位高度、寬度
        // row1.setHeightInPoints(50);
        // sheet.setColumnWidth(0, 12 * 512); // 設定欄位寬度
        // sheet.autoSizeColumn(0); //自動調整欄位寬度,字變大調的不是很好,0是指A欄
        try (FileOutputStream out = new FileOutputStream("D:/Excel.xls")) {
            excelbook.write(out);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
        System.out.println("檔案建立成功!");
    }
}
這裡有官方的範例
這邊也是,點Available Examples的HSSF-Only或XSSF-Only,然後隨便點一個,有很多可參考

※讀檔

HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream("D:/getExcel.xls"));
// HSSFSheet sheet = workbook.getSheet("Sheet1");
HSSFSheet sheet = workbook.getSheetAt(0);// 第一張工作表, 兩種方法擇其一
// 讀取A1
HSSFRow row = sheet.getRow(0);
HSSFCell cell = row.getCell(0);
System.out.println("A1=: " + cell);

2015年7月11日 星期六

泛型中再放泛型的設計

譬如List包Map這樣子:List<Map<String, String>>
首先先設計個像Map的泛型:
class Chess<K, V> {
    private K key;
    private V value;

    public Chess(K key, V value) {
        this.setKey(key);
        this.setValue(value);
    }

    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }
}

然後再設計個像List的泛型
class Msg<T> {
    private T info;

    public Msg(T m) {
        this.setInfo(m);
    }

    public T getInfo() {
        return info;
    }

    public void setInfo(T info) {
        this.info = info;
    }
}

寫個測試類:
Chess<String, Integer> chess = new Chess<>("象棋", 32);
Msg<Chess<String, Integer>> msg = new Msg<>(chess);

System.out.print(msg.getInfo().getKey() + "有");
System.out.println(msg.getInfo().getValue() + "顆棋子!");

結果: 象棋有32顆棋子!

2015年7月9日 星期四

Annotation

※自定annotation

public @interface Chess {//自動繼承java.lang.annotation的Annotation
    public String key();
}

用法:
/*如果不是叫key,叫value,而且只有宣告一個屬性的時候,有個特殊能力; 就是@Chess(value="xxx"), value=可以不打,變成@Chess("xxx"),
又或者有其他屬性,且全部都有default時,也可以這樣用,所以叫value是有好處的
*/
@Chess(key="宣告的是String,所以打String內容")
class xxx{}

兩個變數以上要這樣:
public @interface Chess {//自動繼承java.lang.annotation的Annotation
    public String key();
    public int value() default 10;//注意只能使用基本型態,不然會報「only primitive type, String, Class, annotation, enumeration are permitted or 1-dimensional arrays thereof」的錯
}

@Chess(key="ooo", value=8)
//@Chess(key="ooo") 因為有預設值10,所以可以不打,沒預設值一定要有,不然會報錯
class xxx{}

至於定義了annotation要做哪些事,要配合reflection,先介紹完內建的annotation再說

-------------------------------------------------------------------------------------------------------------
以下介紹 java.lang 的 Annotation Types,因為annotation裡面還有annotation,可能要看兩次才有辦法了解

※@Deprecated(已過時)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

只要是有過時的方法、屬性…等(可以參考下面的@Target),就可以加這個annotation。
別人要使用時,就會發現有刪除線和警告,內行的就知道此方法有可能快移除了, 既然要移除了,那就代表有新的方法,記得還要提供別人現在都是用什麼方法

※@FuntionalInterface(1.8功能型介面)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {
}

1.8 的新同學,在interface裡以前只能定義抽象,1.8可以用static實作方法,還可以用default的東東寫預設值,其他方法當然和以前一模一樣; 但是,加上這個annotation,只能定義一個方法,少於一個或二個以上都不行,當然不包括default和static

※@Override(覆寫)

原始碼:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

要實作一個介面時,有可能打錯字或大小寫不一樣,那就不是Override了,這時可以加這個annotation來保證一定是override

※@SafeVarargs(安全的變數參數)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {
}

此為1.7才有的,官方舉的範例(我稍為修改過)如下:
public static void doSome(List<String>... stringLists) {
    Object[] array = stringLists;
    List<Integer> tmpList = Arrays.asList(42);
    array[0] = tmpList;
    String s = stringLists[0].get(0);
}

呼叫時,這樣使用:
List<String> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
doSome(list1, list2);

設計者的第3行,傳Integer後,第4行居然把它給array接收,但是編譯器不會發現,
所以到第5行就出 java.lang.Integer cannot be cast to java.lang.String  的錯了
根據這樣的問題,希望設計者能考慮這個問題,所以要設計者增加這個annotation,表示他真的有想過這個問題且已經修正了。
但這不是強制的,如果不修正,且加上這個annotation,執行時照樣會報錯

@SuppressWarnings(抑制警告)

原始碼:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

在1.5有泛型時,如果不給泛型,Eclipse會黃黃的,這不代表你很色,也不代表你拉屎完沒擦屁股,是代表你不指定泛型有可能在runtime時會出錯,所以叫你指定一下,至少在編譯時期就能發現這個錯。
如果你堅持不加泛型,又不想看到黃黃的,就加個@SuppressWarnings(value={"unchecked"})
那麼像unchecked還有什麼呢?我找了好久,找到個連結,這裡
我在網上發現有神人翻譯如下:
all:抑制所有警告
boxing:抑制與封裝/拆裝作業相關的警告
cast:抑制與強制轉型作業相關的警告
dep-ann:抑制與淘汰註釋相關的警告
deprecation:抑制與淘汰的相關警告
fallthrough:抑制與 switch 陳述式中遺漏 break 相關的警告
finally:抑制與未傳回 finally 區塊相關的警告
hiding:抑制與隱藏變數的區域變數相關的警告
incomplete-switch:抑制與 switch 陳述式 (enum case) 中遺漏項目相關的警告
javadoc:抑制與 javadoc 相關的警告
nls:抑制與非 nls 字串文字相關的警告
null:抑制與空值分析相關的警告
rawtypes:抑制與使用 raw 類型相關的警告
resource:抑制與使用 Closeable 類型的資源相關的警告
restriction:抑制與使用不建議或禁止參照相關的警告
serial:抑制與可序列化的類別遺漏 serialVersionUID 欄位相關的警告
static-access:抑制與靜態存取不正確相關的警告
static-method:抑制與可能宣告為 static 的方法相關的警告
super:抑制與置換方法相關但不含 super 呼叫的警告
synthetic-access:抑制與內部類別的存取未最佳化相關的警告
sync-override:抑制因為置換同步方法而遺漏同步化的警告
unchecked:抑制與未檢查的作業相關的警告
unqualified-field-access:抑制與欄位存取不合格相關的警告
unused:抑制與未用的程式碼及停用的程式碼相關的警告

在寫Hibernate時,常常會用到這一段
Query query = session.createQuery("HQL語法");
List<Class名稱> list = query.list();//這行要我unchecked抑制警告

我想說是不是認為query.list()有可能會null,加個if判斷; 還有網路上說可以用
List<Class名稱> list2 = Collections.checkedList(query.list(), Class名稱.class);

結果還是要unchecked抑制警告,反而白忙一場。
有人是這樣解決的,因為Hibernate有一個HibernateUtil,所以他在裡面加個static泛型方法,如下:
public static <T> List<T> list(Query q) {
    @SuppressWarnings("unchecked")//在這裡抑制警告
    List<T> list = q.list();
    return list;
}

使用時,只要像這下面這樣就可以不用抑制警告了
List<class名稱> list = HibernateUtil.list(query);

還可以這樣
List<?> list = query.list();
for(Object o:list){
    ClassName cl = (ClassName) o;
    System.out.println(cl.getEmpno());
    System.out.println(cl.getJob());
}

-------------------------------------------------------------------------------------------------------------
以下介紹 java.lang.annotation 的 Annotation Types

※@Documented(做java文件)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

定義一個Chess的annotation,把它定位在 只能放在方法和TYPE上
@Documented
@Target(value={ElementType.METHOD, ElementType.TYPE})
public @interface Chess {
    public String key();
    public int value() default 32;
}

使用@Chess,和寫一些有的沒的
@Chess(key = "棋", value = 999)
public class Demo {
    /**
    * 哈囉!註解
    * 
    * @return
    */
    @Chess(key = "棋的資訊", value = 881)
    public String getInfo() {
    return "Hello Annotation";
    }
}

在dos裡打上藍色的部分
D:\>cd workspace\Test\src
D:\workspace\Test\src>javadoc -d xxx Demo2.java
Loading source files for package Demo2.java...
javadoc: warning - No source files for package Demo2.java
Constructing Javadoc information...
javadoc: warning - No source files for package Demo2.java
javadoc: error - No public or protected classes found to document.
1 error
2 warnings

D:\workspace\Test\src>javadoc -d xxx Demo.java
Loading source file Demo.java...
Constructing Javadoc information...
Standard Doclet version 1.7.0_60
Building tree for all the packages and classes...
Generating doc\Demo.html...
Demo.java:9: warning - @return tag has no arguments.
Generating doc\package-frame.html...
Generating doc\package-summary.html...
Generating doc\package-tree.html...
Generating doc\constant-values.html...
Building index for all the packages and classes...
Generating doc\overview-tree.html...
Generating doc\index-all.html...
Generating doc\deprecated-list.html...
Building index for all classes...
Generating doc\allclasses-frame.html...
Generating doc\allclasses-noframe.html...
Generating doc\index.html...
Generating doc\help-doc.html...
1 warning
-d xxx 是目錄名稱,也可以不指定,但我生成時檔案加資料夾有16個,會和其他檔案搞混,所以做個資料夾,注意紅色的錯誤,需要有public或protected的class,因為我本來寫在同一個.java裡,但一個.java,只能有一個public,使用protected,也會報「Illegal modifier for the class AA; only public, abstract & final are permitted」的錯,所以只好寫一支新的.java,就可以使用public了。
點index.html就可以看到文件了,中文有些是用\uxxxx的,看起來像是國際化的東東,但是有些又能正常顯示,很詭異

※@Inherited(annotation是否可被繼承)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

先定義一個annotation,記得要用runtime才可以,不然保存在.java和.class沒什麼用
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Chess {
    public String key();
    public String value();
    public int ooo();
}

然後定義父類和子類,子類沒有用annotation
@Chess(key = "keykey", ooo = 10, value = "valval")
class Papa {}
class Son extends Papa{}

測試看看能不能得到父類的annotation
Class cls = Class.forName("Son");
for(Annotation anno:cls.getAnnotations()){
    System.out.println(anno);
}

if(cls.isAnnotationPresent(Chess.class)){
    Chess chess =cls.getAnnotation(Chess.class);
    //以下三行為.annotation的方法名稱
    System.out.println(chess.key());
    System.out.println(chess.value());
    System.out.println(chess.ooo());
}

結果:
@Chess(key=keykey, ooo=10, value=valval)
keykey
valval
10

※@Native

原始碼:
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}

1.8的新同學,這個我也看沒有,不過反正一定是和C++有關的東西!

※@Repeatable(重覆宣告annotation)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    Class<? extends Annotation> value();
}

這也是1.8的新同學,讓annotation可以重覆宣告,例如以前一定要這樣寫
@SuppressWarnings({"unchecked", "unused"})
加上這個annotation就可以這樣用
@Repeatable
@SuppressWarnings("unchecked")
@SuppressWarnings("unused")
也就是有不同的撰寫風格啦

※@Retention(保存範圍)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

@Target有一個@Retention,裡面要放RetentionPolicy,總共分成三種範圍,如下:
                         .java        .class        JVM
SOURCE          ✔
CLASS              ✔             ✔
RUNTIME       ✔             ✔              ✔

CLASS為預設,像@Target是RetentionPolicy.RUNTIME,所以會保存在.java、.class、在執行時也會加載到JVM中

※@Target(限制annotation放在哪)

以上的例子,放在方法、類別…等都可以,如果要限制只能放在enum上呢?
原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

value裡面要放 ElementType,API 有詳細的介紹,且英文單字不難,但它的連結好像沒做書籤,所以我把它複製並翻譯過來:
ANNOTATION_TYPE   宣告在annotation
CONSTRUCTOR            宣告在建構子
FIELD                              宣告在屬性(包括Enum)
LOCAL_VARIABLE      宣告在區域變數
METHOD                        宣告在方法
PACKAGE                      宣告在package
PARAMETER                 宣告在參數
TYPE                               宣告在類別、介面(包括annotation)、enum
以下兩個為1.8新增的
TYPE_PARAMETER     宣告在TYPE的角括號(<>),(Type parameter declaration)
TYPE_USE                      宣告在各式型態(Use of a type)

所以ElementType.ANNOTATION_TYPE只能宣告在annotation裡; enum可以用ElementType.Field 或 ElementType.Type

我親自試過後,
ANNOTATION_TYPE:宣告在annotation,包括目前自己定義的annotation和annotation裡的方法,但其他的annotation裡的方法不行,如下:
@ABC
@Target(ElementType.ANNOTATION_TYPE)
@interface ABC {
    @ABC//如果這@interface不是ABC就不行
    public String value() default "";
}

CONSTRUCTOR:除了建構子,還包括自己的annotation和annotation裡的方法,如上面的例子,都可以
FIELD:除了全域變數,還包括自己的annotation和annotation裡的方法,API說包括enum,其實指得是enum裡的field
LOCAL_VARIABLE:除了區域變數,還包括自己的annotation和annotation裡的方法
METHOD:除了方法,還包括自己的annotation和annotation裡的方法,還有其他annotation的方法都OK
PACKAGE:直接定義會出現「Package annotations must be in file package-info.java」的錯,所以我只好新增一個叫package-info.java的檔,打開以後是空的,有錯誤提示「The declared package "" does not match the expected package "xxx"」,所以我就宣告個package xxx,然後在前面用annotation就可以了,原來的檔不能定義。
當然還包括自己的annotation和annotation裡的方法
PARAMETER:宣告在()裡,還包括自己的annotation和annotation裡的方法
TYPE:宣告在class、interface、annotation、enum,abstract算class的一種,當然還包括自己的annotation和annotation裡的方法
TYPE_PARAMETER:宣告在泛型類別和泛型方法,還有自己的annotation。注意自己的annotation方法不行,還有List、Set、Map裡的<>也不行
TYPE_USE:只有一個不行java.lang.String不行(不是String不行,只要是寫完整路徑就不行,很奇怪; 我只寫String是OK的,滑鼠移上去,也真的是java.lang.String,但就是一個可以,一個不行),是完整路徑不行喔!譬如java.util.Map不行,但import在上面,只寫Map卻可以
@Target都不寫:TYPE_PARAMETER不行以外,其他都可以
全部都可以:要這樣設@Target({ ElementType.TYPE_USE, ElementType.FIELD, ElementType.LOCAL_VARIABLE }),但兩個以上不知為什麼,Eclipse沒提示

P.S. 自己定義的@interface一直都可以,只有TYPE_PARAMETER,自己的方法不行
List、Set、Map,<>裡定義annotation,只有TYPE_USE可以,連不寫@Target都不行
所以依照這樣的關係,我整理了一張表(我測的是1.8.0_25):

※使用annotation並做事


先寫一支annotation,我加個繼承
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Chess {
    public String key() default "我是key";
    public String value();
    public int ooo() default 32;
}

然後有一個爸爸類,類和方法都有加annotation,爸爸有兩個兒子繼承,第一個兒子覆寫爸爸的方法,第二個兒子什麼都沒有,而且兒子都沒有用@Chess
@Chess(key = "keykey", ooo = 10, value = "valval")
class Papa {
    @Chess("我是value")
    public String getInfo() {
        return "Papa的getInfo()";
    }
}

class FirstSon extends Papa {
    @Override
    public String getInfo() {
        return "FirstSon的getInfo()";
    }
}

class SecondSon extends Papa {
}

寫個測試類來測一下:
System.out.println("----------------Papa-------------------");
Class<?> papaClass = Class.forName("Papa");

System.out.println("從class取得annotation,然後取得一個一個屬性");
Chess papaChess = papaClass.getAnnotation(Chess.class);
System.out.println(papaChess.ooo());
System.out.println(papaChess.key());
System.out.println(papaChess.value());

System.out.println("從class取得annotations,然後取得全部屬性");
for (Annotation ann : papaClass.getAnnotations()) {
    System.out.println(ann);
}

System.out.println("從方法取得annotation,然後取得一個一個屬性");
Chess papaChessMethod = papaClass.getMethod("getInfo").getAnnotation(
        Chess.class);
System.out.println(papaChessMethod.ooo());
System.out.println(papaChessMethod.key());
System.out.println(papaChessMethod.value());

System.out.println("從方法取得annotation,然後取得全部屬性");
for (Annotation ann : papaClass.getMethod("getInfo").getAnnotations()) {
    System.out.println(ann);
}

System.out.println("----------------SecondSon--------------");
Class<?> secondClass = Class.forName("SecondSon");

Chess secondChess = secondClass.getAnnotation(Chess.class);
System.out.println("從class取得annotation,然後取得一個一個屬性");
if (secondChess != null) {
    System.out.println(secondChess.ooo());
    System.out.println(secondChess.key());
    System.out.println(secondChess.value());
}

System.out.println("從class取得annotations,然後取得全部屬性");
for (Annotation ann : secondClass.getAnnotations()) {
    System.out.println(ann);
}

System.out.println("從方法取得annotation,然後取得一個一個屬性");
Chess secondChessMethod = secondClass.getMethod("getInfo")
        .getAnnotation(Chess.class);
if (secondChessMethod != null) {
    System.out.println(secondChessMethod.ooo());
    System.out.println(secondChessMethod.key());
    System.out.println(secondChessMethod.value());
}

System.out.println("從方法取得annotation,然後取得全部屬性");
for (Annotation ann : secondClass.getMethod("getInfo").getAnnotations()) {
    System.out.println(ann);
}

System.out.println("----------------FirstSon---------------");
Class<?> firstClass = Class.forName("FirstSon");

Chess firstChess = firstClass.getAnnotation(Chess.class);
System.out.println("從class取得annotation,然後取得一個一個屬性");
if (firstChess != null) {
    System.out.println(firstChess.ooo());
    System.out.println(firstChess.key());
    System.out.println(firstChess.value());
}

System.out.println("從class取得annotations,然後取得全部屬性");
for (Annotation ann : firstClass.getAnnotations()) {
    System.out.println(ann);
}

System.out.println("從方法取得annotation,然後取得一個一個屬性");
Chess firstChessMethod = firstClass.getMethod("getInfo").getAnnotation(
        Chess.class);
if (firstChessMethod != null) {
    System.out.println(firstChessMethod.ooo());
    System.out.println(firstChessMethod.key());
    System.out.println(firstChessMethod.value());
}

System.out.println("從方法取得annotation,然後取得全部屬性");
for (Annotation ann : firstClass.getMethod("getInfo").getAnnotations()) {
    System.out.println(ann);
}

結果:
----------------Papa-------------------
從class取得annotation,然後取得一個一個屬性
10
keykey
valval
從class取得annotations,然後取得全部屬性
@Chess(ooo=10, key=keykey, value=valval)
從方法取得annotation,然後取得一個一個屬性
32
我是key
我是value
從方法取得annotation,然後取得全部屬性
@Chess(ooo=32, key=我是key, value=我是value)
----------------SecondSon--------------
從class取得annotation,然後取得一個一個屬性
10
keykey
valval
從class取得annotations,然後取得全部屬性
@Chess(ooo=10, key=keykey, value=valval)
從方法取得annotation,然後取得一個一個屬性
32
我是key
我是value
從方法取得annotation,然後取得全部屬性
@Chess(ooo=32, key=我是key, value=我是value)
----------------FirstSon---------------
從class取得annotation,然後取得一個一個屬性
10
keykey
valval
從class取得annotations,然後取得全部屬性
@Chess(ooo=10, key=keykey, value=valval)
從方法取得annotation,然後取得一個一個屬性
從方法取得annotation,然後取得全部屬性


P.S. 注意第一個兒子他覆寫爸爸的方法,所以通過方法取不到annotation; 而第二個兒子都取得到

如果把@Inherited拿掉的結果如下:
----------------Papa-------------------
從class取得annotation,然後取得一個一個屬性
10
keykey
valval
從class取得annotations,然後取得全部屬性
@Chess(ooo=10, key=keykey, value=valval)
從方法取得annotation,然後取得一個一個屬性
32
我是key
我是value
從方法取得annotation,然後取得全部屬性
@Chess(ooo=32, key=我是key, value=我是value)
----------------SecondSon--------------
從class取得annotation,然後取得一個一個屬性
從class取得annotations,然後取得全部屬性
從方法取得annotation,然後取得一個一個屬性
32
我是key
我是value
從方法取得annotation,然後取得全部屬性
@Chess(ooo=32, key=我是key, value=我是value)
----------------FirstSon---------------
從class取得annotation,然後取得一個一個屬性
從class取得annotations,然後取得全部屬性
從方法取得annotation,然後取得一個一個屬性
從方法取得annotation,然後取得全部屬性

P.S. 第一個兒子什麼都取不到了,第二個兒子通過方法,還是可以取得老爸的annotation
所以我試的結果,@Inherited只有對類別有用的樣子


※java 9 新增 MODULE

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.MODULE)
public @interface Uuu {
    String value();
}

※新增一個 Uuu 使用在 module 上

@Uuu("")
module Xxx {
    ...
}

可以在 module-info 使用

2015年7月8日 星期三

Oracle 的TO_CHAR,小心第二個參數

SELECT TO_CHAR(3, '000') VALUE, LENGTH(TO_CHAR(3, '000')) LENGTH FROM DUAL;

SELECT TO_CHAR(TO_NUMBER(3), '000') VALUE, LENGTH(TO_CHAR(TO_NUMBER(3), '000')) LENGTH FROM DUAL;

SELECT TO_CHAR('3', '000') VALUE, LENGTH(TO_CHAR('3', '000')) LENGTH FROM DUAL;

以上三行都是 VALUE LENGTH 003 4 注意003的左邊有一個空格 但是下面這行是正確的6
SELECT LENGTH(TO_CHAR('abc' || 'def')) FROM DUAL;

也就是有使用第二個參數時,才會在前面有前導字元的情形,它用來分辯+-的,可以trim掉或者使用「FM000」

2015年7月6日 星期一

Struts的 validate framework(Struts三)

尤於validate()好用,上次的dynaActionForm,如果不驗證,確實是不用寫form,但大部分都要驗證啊!這時就有白寫的感覺,所以有這個框架,讓validate可驗證,且ActionForm不用寫

1.刪除ActionForm
2.struts-config.xml加入plug-in,從blank copy,注意看value有兩個xml:validator-rules.xml(通常不需修改,包在jar檔內)和validation.xml struts-core-1.3.10.jar下的org.apache.struts.validator有validator-rules.xml
3.copy validateion.xml,還是從blank copy,copy到1的value的地方
4.struts-config.xml修改如下:
<form-beans>
    <!--type改成框架路徑-->
    <form-bean name="ooo" type="org.apache.struts.validator.DynaValidatorForm">
        <form-property name="username" type="java.lang.String" />
        <form-property name="password" type="java.lang.String" />
    </form-bean>
</form-beans>

Action修改如下
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception {
    DynaValidatorForm dvf = (DynaValidatorForm) form;
    String username = (String) dvf.get("username");
    String password = (String) dvf.get("password");

    // action想存錯誤訊息可用這兩行
    ActionErrors errors = new ActionErrors();
    request.setAttribute(Globals.ERROR_KEY, errors);

    if (username.trim().equals("aaa") && password.trim().equals("111")) {
        return mapping.findForward("success");
    } else {
        errors.add("user", new ActionMessage("actionUP"));
        return mapping.findForward("fail");
    }
}

vadation.xml設定如下:
官方文件:struts-1.3.10/docs/faqs/validator.html
.depends可以打很多,用逗號隔開,要參考validator-rules.xml
.validator-rules.xml的required有msg="errors.required",用搜尋可得key=value,
把它整段復製到自己的properties
.name為depends裡的其中一項,key是properties的key,給{0}用的
.msg是自訂訊息; arg是用validator-rules.xml的訊息
<form name="ooo">
    <field property="username" depends="required">
    <!-- <arg key="vusername" name="required" position="0" /> -->
        <msg name="required" key="username"/>
    </field>
    <field property="password" depends="required">
        <arg key="vpassword" name="required" position="0" />
    </field>
</form>

properties
username=username can't empty
password=password can't empty 
actionUP=please Enter legal username and password!
errors.required={0} is required.
vusername=\u5E33\u865F
vpassword=\u5BC6\u78BC

index.jsp的property要改成和validation.xml的property一樣
<html:errors/>
<form action="<c:url value="/xxx.do"/>">
    帳:<input type="text" name="username" /><html:errors property="username" /><br />
    密:<input type="password" name="password" /><html:errors property="password" /><br />
    <input type="submit" value="送出" />
    <input type="reset" value="重置" />
</form>

其他檔案都和Struts(一)裡的一樣

※多個驗證可修改如下,如密碼是必填也必須是數字:

<form name="ooo">
    <field property="username" depends="required">
    <!-- <arg key="vusername" name="required" position="0" /> -->
        <msg name="required" key="username"/>
    </field>
    <field property="password" depends="required, integer">
        <arg key="vpassword" name="required" position="0" />
        <arg key="vpassword" name="integer" position="0" />
    </field>
</form>
properties也要多增加一行,從validator-rules.xml copy
username=username can't empty
password=password can't empty 
actionUP=please Enter legal username and password!
errors.required={0} is required.
vusername=\u5E33\u865F
vpassword=\u5BC6\u78BC
errors.integer={0} must be an integer.

depends加多個用逗點隔開即可,但arg的name加多個也是可以run,但key就會變成null

※date型態和範圍稍為難一點,我新增兩個欄位如下:

index.jsp
<form action="<c:url value="/xxx.do"/>">
    帳:<input type="text" name="username" />
      <html:errors property="username" /><br />
    密:<input type="password" name="password" />
      <html:errors property="password" /><br />
    範圍:<input type="text" name="intRangeTest" />
      <html:errors property="intRangeTest" /><br />
    日期:<input type="text" name="dateTest" />
      <html:errors property="dateTest" /><br />
    <input type="submit" value="送出" />
    <input type="reset" value="重置" />
</form>

struts-config.xml也要增加,注意日期的type是String,我用java.util.Date會報錯
<form-bean name="ooo" type="org.apache.struts.validator.DynaValidatorForm">
    <form-property name="username" type="java.lang.String" />
    <form-property name="password" type="java.lang.String" />
    <form-property name="intRangeTest" type="java.lang.String" />
    <form-property name="dateTest" type="java.lang.String" />
</form-bean>

validation.xml範圍我設定100~2000,日期驗證官方說是用SimpleDateFormat:
<form name="ooo">
    <field property="username" depends="required">
    <!-- <arg key="vusername" name="required" position="0" /> -->
        <msg name="required" key="username"/>
    </field>
    <field property="password" depends="required, integer">
        <arg key="vpassword" name="required" position="0" />
        <arg key="vpassword" name="integer" position="0" />
    </field>
    <field property="intRangeTest" depends="intRange">
        <arg position="0" key="vintRangeTest"/>
        <arg position="1" name="intRange" key="${var:min}" resource="false"/>
        <arg position="2" name="intRange" key="${var:max}" resource="false"/>
        <var>
            <var-name>min</var-name>
            <var-value>100</var-value>
        </var>
        <var>
            <var-name>max</var-name>
            <var-value>2000</var-value>
        </var>
    </field>
    <field property="dateTest" depends="date, required">
        <arg position="0" key="vdate" name="date"/>
        <var>
            <var-name>datePattern</var-name>
            <var-value>yyyyMMdd</var-value>
        </var>
        <arg key="vdate" name="required" position="0" />
    </field>
</form>

properties:
username=username can't empty
password=password can't empty 
actionUP=please Enter legal username and password!
errors.required={0} is required.
vusername=\u5E33\u865F
vpassword=\u5BC6\u78BC
errors.integer={0} must be an integer.
vintRangeTest=\u7BC4\u570D
errors.range={0} is not in the range {1} through {2}.
errors.date={0} is not a date.
vdate=\u65E5\u671F

※validwhen

當所有的驗證都不合乎需求時,那只好自己寫了
將jsp,struts-config都恢復成帳號和密碼兩個欄位
properties,validwhen需要用errors.required,但訊息不是我們要的,先改一下
username=username can't empty
password=password can't empty 
actionUP=please Enter legal username and password!
errors.required={0} \u9A57\u8B49\u5931\u6557
vusername=\u5E33\u865F
vpassword=\u5BC6\u78BC
errors.integer={0} must be an integer.

validation.xml,<var-value>裡面的語言自成一格,
*this*:為自己的欄位; 要抓其他欄位只要寫property裡的名字即可
寫完一定要用()包起來,否則會出「SYSTEM ERROR: Check logs for details.」的錯
<field property="username" depends="required">
    <msg name="required" key="username"/>
</field>
<field property="password" depends="integer, validwhen">
    <arg key="vpassword" name="integer" position="0" />
    <arg key="vpassword" name="validwhen" position="0" />
    <var>
        <var-name>test</var-name>
        <var-value>((*this* == '111') and (username == "aaa"))</var-value>
    </var>
</field>

※客戶端驗證

之前的全都是伺服器端的驗證,Struts也支緩客戶端驗證。 其實就是把剛剛的錯誤訊息變成alert出來
1.<html>裡加屬性 formName
2.<form>增加name和onsubmit屬性
3.我試的結果只有required (必填)有效

<html:javascript formName="ooo" /><%-- 改這行--%>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=BIG5">
    <title>Insert title here</title>
    </head>
    <body>
        <form action="<c:url value="/xxx.do"/>" name="ooo" onsubmit="validateOoo(this)"><%-- 改這行--%>
            帳:<input type="text" name="username" />
              <html:errors property="username" /><br />
            密:<input type="password" name="password" />
              <html:errors property="password" /><br />
            <input type="submit" value="送出" />
            <input type="reset" value="重置" />
        </form>
    </body>
</html>