2015年8月31日 星期一

結合VO的問題 (Struts2.3.x 四)

再寫一個頁面來測試:
VO:
public class Chess {
    private String name;
    private int price;
    private Date date;
    //setter/getter...
}

Action:
public class ChessAction extends ActionSupport {
    Chess chess = new Chess();
    //setter/getter...
    @Override
    public String execute() throws Exception {
        System.out.println("getName=" + chess.getName());
        System.out.println("getPrice=" + chess.getPrice());
        System.out.println("getDate=" + chess.getDate());
        return SUCCESS;
    }
}

struts-a.xml多一個action
<package name="basicstruts2" extends="struts-default" namespace="/struts2">
    <action name="login" class="login.LoginAction">
        <result name="success">/struts2/login.jsp</result>
        <result name="input">/struts2/login.jsp</result>
    </action>
    <action name="chess" class="login.ChessAction">
        <result name="success">/struts2/chess.jsp</result>
        <result name="input">/struts2/chess.jsp</result>
    </action>
</package>

chess.jsp
<form action="chess.action">
    <input type="text" name="chess.name" value="Johnson" /><br />
    <input type="text" name="chess.price" value="50" /><br />
    <input type="text" name="chess.date" value="1988/08/08 12:34:56" /><br />
    <input type="reset" />
    <input type="submit" />
</form>

jsp頁面的格式可用main方法打如下的語法測試:
System.out.println(DateFormat.getDateTimeInstance().format(new Date()));

這樣才知道格式要打什麼,不然會往struts-a.xml設定的input頁跳轉;
如果input頁沒設,會出404,找不到input的錯誤
所以字串、數字、日期都可以自動轉換成java的型式
如果數字打字串會錯,還有日期我試的結果,年月日是OK的,但時分秒都是0

如果EL失效,就是${xxx}會直接印出來,就在JSP加<%@ page isELIgnored="false"%>

2015年8月30日 星期日

Mybatis-generator Mybatis3.x(十二)

mybatis-generator為自動生成映射檔的工具
1.create一張table,例如EMP,請自行增加
2.首先用maven下載,去這裡
3.建立一個資料夾,例如叫tempory,然後將mybatis-generator-core-x.x.x.jar和資料庫驅動程式放進去
4.建立xml,從這裡copy,也是放到建立好的資料夾
5.修改xml
<generatorConfiguration>
    <classPathEntry location="D:\tempory\ojdbc7.jar" />

    <context id="DB2Tables" targetRuntime="MyBatis3">
        <jdbcConnection driverClass="oracle.jdbc.driver.OracleDriver"
            connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:orcl" userId="your account"
            password="your password">
        </jdbcConnection>

        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <javaModelGenerator targetPackage="test.model"
            targetProject="D:\tempory">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <sqlMapGenerator targetPackage="xml"
            targetProject="D:\tempory">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>

        <javaClientGenerator type="XMLMAPPER"
            targetPackage="test.dao" targetProject="D:\tempory">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>

        <table tableName="EMP" />
    </context>
</generatorConfiguration>

6.打開dos後,從這裡copy語法
例如:d:\tempory>java -jar mybatis-generator-core-1.3.2.jar -configfile generatorConfi
g.xml -overwrite
成功會出訊息:MyBatis Generator finished successfully.
有錯就注意看訊息,單字還蠻簡單的

講解:
context id="DB2Tables"是官方的,反正只是個id,不要是因為這樣就以為是DB2了
最好改成合乎的名字
javaTypeResolver:VO是否強制使用java.math.BigDecimal
還可以不要生成註解
<commentGenerator>    
    <property name="suppressAllComments" value="true"/>
</commentGenerator>

table 有很多屬性可設置,如dao有不喜歡的方法,可以在這設false如下:
<table tableName="EMP" enableCountByExample="false" 
    enableUpdateByExample="false" enableDeleteByExample="false" 
    enableSelectByExample="false" selectByExampleQueryId="false">
</table>
可以點官網左邊的XML Configuration Reference參考

2015年8月22日 星期六

TypeHandler(類型處理器) Mybatis3.x(十)

TypeHandler就是將型態轉成另一種型態

※事前規劃

CREATE TABLE TEST_BOOLEAN(
    ID NUMBER,
    NAME VARCHAR(10),
    BOO NUMBER,
    CONSTRAINT TEST_BOOLEAN_PK PRIMARY KEY(ID)
);
COMMENT ON TABLE TEST_BOOLEAN IS '測試布林表';
COMMENT ON COLUMN TEST_BOOLEAN.ID IS 'id';
COMMENT ON COLUMN TEST_BOOLEAN.NAME IS '名字';
COMMENT ON COLUMN TEST_BOOLEAN.BOO IS '0表示false';

INSERT INTO TEST_BOOLEAN(ID, NAME, BOO)VALUES(11, 'tom', 0);
INSERT INTO TEST_BOOLEAN(ID, NAME, BOO)VALUES(22, 'jerry', 1);
INSERT INTO TEST_BOOLEAN(ID, NAME, BOO)VALUES(33, 'apple', 2);
INSERT INTO TEST_BOOLEAN(ID, NAME, BOO)VALUES(44, 'banana', 1);
INSERT INTO TEST_BOOLEAN(ID, NAME, BOO)VALUES(55, 'guava', 0);

尤於資料庫沒有布林值,所以用1和0代表true和false
官方的說法有兩種方法,實作TypeHandler介面和繼承BaseTypeHandler

※TypeHandler

先寫一隻實作這介面並覆寫方法
getResult:將資料庫的數字轉成java的boolean
setParameter:將java的boolean轉成資料庫的數字
@MappedTypes({Boolean.class})
@MappedJdbcTypes({JdbcType.INTEGER})
public class BooleanIntegerTransfor implements TypeHandler<Boolean> {
    @Override
    public Boolean getResult(ResultSet rs, String columnName)
            throws SQLException {
        System.out.println("columnName=" + columnName);
        System.out.println(rs.getString(columnName));
        
        return rs.getShort(columnName) != 0;
    }

    @Override
    public Boolean getResult(ResultSet rs, int columnIndex) throws SQLException {
        System.out.println("rs columnIndex=" + columnIndex);
        System.out.println(rs.getString(columnIndex));
        return rs.getInt(columnIndex) != 0;
    }

    @Override
    public Boolean getResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        System.out.println("cs columnIndex=" + columnIndex);
        System.out.println(cs.getString(columnIndex));
        return cs.getInt(columnIndex) != 0;
    }

    @Override
    public void setParameter(PreparedStatement ps, int i, Boolean parameter,
            JdbcType jdbcType) throws SQLException {
        System.out.println("i=" + i);
        System.out.println("parameter=" + parameter);
        if (parameter == null) {
            ps.setNull(i, java.sql.Types.NULL);
        } else {
            if (parameter) {
                ps.setInt(i, 1);
            } else {
                ps.setInt(i, 0);
            }
        }
    }
}

vo只要注意boo是要轉換的類型,所以雖然資料庫是number,但在java還是寫boolean,然後setter/getter

設定檔的部分,看要偵對整個package或剛剛實作的java,javaType和jdbcType可用註解的方式,如實作的類上面那兩行,取其一種方式:
<typeHandlers>
<!--     <package name="org.mybatis.model" /> -->
    <typeHandler handler="org.mybatis.model.BooleanIntegerTransfor" javaType="java.lang.Boolean" jdbcType="INTEGER" />
</typeHandlers>

vo的xml要注意resultMap的javaType和jdbcType要打才會執行實作的類,而insert也要打,才能呼叫到setParameter方法:
<mapper namespace="a.b.c">
    <sql id="column">
        ID, NAME, BOO
    </sql>

    <resultMap type="TestBoolean" id="TestBooleanInterface">
        <id property="id" column="ID" />
        <result property="name" column="NAME" />
        <result property="boo" column="BOO" javaType="java.lang.Boolean" jdbcType="INTEGER" />
    </resultMap>

    <insert id="insert" parameterType="TestBoolean">
        INSERT INTO TEST_BOOLEAN (
            <include refid="column" />
        ) VALUES (
             #{id}, #{name}, #{boo, javaType=java.lang.Boolean, jdbcType=INTEGER}
        )
    </insert>

    <select id="findAll" resultMap="TestBooleanInterface">
        SELECT <include refid="column" /> FROM TEST_BOOLEAN
    </select>
</mapper>

測試類:
SqlSession sqlSession = MyBatisSessionFactory.getSession();
List<TestBoolean> all = sqlSession.selectList("a.b.c.findAll");
for (TestBoolean tb : all) {
    System.out.println("a=" + tb.getId());
    System.out.println("b=" + tb.getName());
    System.out.println("c=" + tb.getBoo());
}

TestBoolean tb = new TestBoolean();
tb.setId(78);
tb.setName("brush");
tb.setBoo(true);
System.out.println(sqlSession.insert("a.b.c.insert", tb));

sqlSession.commit();
MyBatisSessionFactory.closeSession();

心得:
.還有兩個getResult方法,不知怎麼呼叫
.javaType、jdbcType和@MappedTypes、@MappedJdbcTypes這兩組不打或兩組都打,我試也是ok的,可能有它的原理,但我覺得這樣不直覺
.只要打一組,大家都看得懂,好維護。 不打或全打嘛!搞不好哪天mybatis新版就不能使用了也說不定。
.我的這篇文章,講Annotation的,裡面有個@Target,1.8有新增一個叫TYPE_USE,理論上應該哪裡都能使用才對,但我測出來的不是這樣,所以才有那一張表; 但我覺得哪一天版本更新就都可以了也說不定,所以不用太過研究這種不好的設計
※BaseTypeHandler
@MappedTypes({ Boolean.class })
@MappedJdbcTypes({ JdbcType.INTEGER })
public class BooleanIntegerTransfor extends BaseTypeHandler<Boolean> {
    @Override
    public Boolean getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        return rs.getInt(columnName) != 0;
    }

    @Override
    public Boolean getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        return rs.getInt(columnIndex) != 0;
    }

    @Override
    public Boolean getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        return cs.getInt(columnIndex) != 0;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
            Boolean parameter, JdbcType jdbcType) throws SQLException {
        System.out.println(parameter);
        if (parameter == true) {
            ps.setInt(i, 1);
        } else {
            ps.setInt(i, 0);
        }
    }
}


攔截器 Mybatis3.x(十一)

攔截器就是直接某個動作時,攔截下來做某件事,和PL/SQL的TRIGGER類似
先不管mybatis的設定,先寫個main method,注意內部類別:
public static void main(String[] args) {
    List<String> list = (List<String>) new MyInterceptor()
            .plugin(new ArrayList<>());
    list.add(0, "aaa");
}

@Intercepts(value = { @Signature(args = { int.class, Object.class }, method = "add", type = List.class) })
private static class MyInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("intercept");
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        System.out.println("plugin");
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        System.out.println("setProperties");
    }
}

結果:
plugin
intercept

.args是小括弧的參數
.只要執行了add方法,就會攔截
.setProperties會先執行,但目前還沒寫,再來是plugin method,最後是intercept method
.如果想攔截其他方法,但沒有參數,就寫args = {}
.只要設定有錯,就會拋找不到方法的錯誤訊息

※先用Mybatis3.x(九)的例子,可以run再說

由於我Mybatis3.x(九)的程式碼不見了,只好再做一次了,這一次做不一樣的好了
CREATE TABLE DEPT (
    DEPTNO NUMBER NOT NULL, 
    DNAME VARCHAR2(255 BYTE), 
    LOC VARCHAR2(255 BYTE), 
    CONSTRAINT DEPT_PK PRIMARY KEY(DEPTNO)
);
INSERT INTO DEPT (DEPTNO,DNAME,LOC) VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO DEPT (DEPTNO,DNAME,LOC) VALUES (20, 'RESEARCH', 'DALLAS');
INSERT INTO DEPT (DEPTNO,DNAME,LOC) VALUES (30, 'SALES', 'CHICAGO');
INSERT INTO DEPT (DEPTNO,DNAME,LOC) VALUES (40, 'OPERATIONS', 'BOSTON');

vo類,故意和資料庫取不一樣的名字
public class Department {
    private Integer deptno;
    private String dname;
    private String loc;
    //setter/getter...
}

DAO:
public interface IDepartmentDAO {
    @Insert("INSERT INTO DEPT(DEPTNO, DNAME, LOC) VALUES (#{deptno}, #{dname}, #{loc})")
    public boolean insert(Department vo);

    @Select("SELECT DEPTNO, DNAME, LOC FROM DEPT")
    public List<Department> findAll();
}

mybatis-config.xml最後要加這一段,有四種方式,可參考Mybatis3.x(九)
<mappers>
    <mapper class="dao.IDepartmentDAO"/>
</mappers>

測試類:
SqlSession sqlSession = MybatisUtil.getSession();

//查詢
IDepartmentDAO dao = sqlSession.getMapper(IDepartmentDAO.class);
Iterator<Department> iter = dao.findAll().iterator();
while (iter.hasNext()) {
    Department dept = iter.next();
    System.out.println("getDeptno=" + dept.getDeptno());
    System.out.println("getDname=" + dept.getDname());
    System.out.println("getLoc=" + dept.getLoc());
    System.out.println("=========================");
}

//新增
Department dept2 = new Department();
dept2.setDeptno(50);
dept2.setDname("MONEY");
dept2.setLoc("FRANCH");
IDepartmentDAO dao2 = sqlSession.getMapper(IDepartmentDAO.class);
System.out.println(dao2.insert(dept2));

sqlSession.commit();
MybatisUtil.closeSession();

都可以run後,再加攔截器的程式碼

※攔截器

有兩個地方要加
mybatis-config.xml加這一段,注意順序,錯誤會有提示
<plugins>
    <plugin interceptor="plugin.MyInterceptor">
        <property name="param1" value="abc" />
        <property name="param2" value="123" />
    </plugin>
</plugins>

繼承Interceptor的類別:
@Intercepts(value = {
        @Signature(args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class }, method = "query", type = Executor.class),
        @Signature(args = { Connection.class }, method = "prepare", type = StatementHandler.class) })
public class MyInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        System.err.println("result=" + result);
        return result;
    }

    @Override
    public Object plugin(Object target) {
        System.err.println("target=" + target);
        return Plugin.wrap(target, this);
    }

    // 取得properties,設定在mybatis-config.xml
    @Override
    public void setProperties(Properties properties) {
        String param1 = properties.getProperty("param1");
        String param2 = properties.getProperty("param2");
        System.err.println("param1 = " + param1);
        System.err.println("param2 = " + param2);
    }
}

官方提供四種支援如下:
.org.apache.ibatis.executor.Executor
.org.apache.ibatis.executor.parameter.ParameterHandler
.org.apache.ibatis.executor.resultset.ResultSetHandler
.org.apache.ibatis.executor.statement.StatementHandler
其中StatementHandler又有兩個實作類別
BaseStatementHandler(抽象)和RoutingStatementHandler
BaseStatementHandler又有三個兒子
CallableStatementHandler:處理CallableStatement
PreparedStatementHandler:處理PreparedStatement
SimpleStatementHandler:處理Statement

.第一個Signature針對查詢,主要用
abstract <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)

.第二個Signature有連資料庫就在呼叫方法之前攔截,所以增刪改查都OK
abstract Statement prepare(Connection connection)

.測試時,result有印出來才表示有成功攔截
.使用這些方法時,我是理解出來的,因為官方網站的API,都沒寫註解,原始碼我也看不太懂

2015年8月21日 星期五

多個struts.xml 與 國際化訊息 (Struts2.3.x 三)

※多個struts.xml

一個專案常很多人開發,每個人都有一份struts.xml,所以會衝到,可以利用如下方法整合:
<struts>
    <include file="struts-a.xml" />
    <include file="struts-b.xml" />
</struts>

可以加很多的include,我的struts-a.xml複製後改名struts-b.xml居然可以run,
因為package的name,namespace,action的name完全一樣,
如果前端要求aaa.action,那到底是哪一個會起作用,所以這是不好的設計,所以多人開發時,必需先開會討論好名稱會比較好

※資源文件

之前的Action裡的顯示訊息是寫死的,必需把它搬出來
有三種方式:
.在同一個package下,和class名稱相同的properties
.同一個package共用的properties,必需叫package.properties
.全域的properties:前面兩個很少用,主要用這一個,要在struts.xml設定
.如果不按照以上的規則走,也不會有錯誤,只是錯誤訊息會變成key,
也就是super.getText()裡面的字

先測第一種同class名稱的
LoginAction.properties:
login.success=login success
login.failure=login fail

Action也要改:
@Override
public String execute() throws Exception {
    if (userName.equals("aaa") && userPassword.equals("123")) {
        displayMessage = super.getText("login.success");
        return ActionSupport.SUCCESS;
    } else {
        displayMessage = super.getText("login.failure");
        return Action.INPUT;
    }
}

測過後,將LoginAction.properties改成package.properties再測一次
最後在新增一個package,將properties剪過去,看有沒有用,我是測失敗,所以是正常的

※國際化

也就是改中文就顯示中文,改英文就顯示英文
瀏覽器可以改語言,我現在IE是用11版,和以前的版本的設定不太一樣
在src下新增properties,我是maven,所以是在src/main/java下
將剛剛的properties copy過來改,如下:
testMessage_en_US.properties:
login.success=login success
login.failure=login fail

testMessage_zh_TW.properties:
login.success=\u767b\u9304\u6210\u529f
login.failure=\u767b\u9304\u5931\u6557
中文自行用native2ascii.exe工具

加好後,struts.xml也要設定
首先先打開struts2-core-2.x.x.jar裡的org.apache.struts2,有個default.properties
找到struts.custom.i18n.resources,複製起來
然後在struts.xml加上constant
<struts>
    <constant name="struts.custom.i18n.resources" value="testMessage" />
    <package name="basicstruts2" extends="struts-default" namespace="/struts2">
        <action name="login" class="login.LoginAction" method="execute">
            <result name="success">/struts2/login.jsp</result>
            <result name="input">/struts2/login.jsp</result>
        </action>
    </package>
</struts>
value為zh_TW前面的名稱
constant也可以在src/main/java下新增一個叫struts.properties(一定要這樣取),然後將
struts.custom.i18n.resources=testMessage貼進去即可
也就是default.properties有兩種實作方式,個人感覺用struts.properties較好,
因為properties對應properties;而struts.xml就管其他的部分就好了
記得測試全部都是從action轉jsp哦

亂碼的部分,因為default.properties裡的struts.i18n.encoding,預設已經打開,而且是UTF-8,所以就不用設了

2015年8月20日 星期四

Struts2 HelloWorld整理 與 伺服器/用戶端 跳轉 (Struts2.3.x 二)

重新寫了一隻HelloWorld,登入用的
web.xml還是一樣,struts.xml如下:
<struts>
    <constant name="struts.devMode" value="true" />
    <package name="basicstruts2" extends="struts-default" namespace="/struts2">
        <action name="login" class="login.LoginAction" method="execute">
            <result name="success">/struts2/login.jsp</result>
            <result name="input">/struts2/fail.jsp</result>
        </action>
    </package>
</struts>

devMode的作用:
.更改.properties配置文件後,不用重啟Server即生效。此功能也可設置struts.i18n.reload=true
.更改struts.xml後,不用重啟Server即生效。此功能也可設置struts.configuration.xml.reload=true
.改成true之後,效能會便慢
.可參考這裡
.namespace,預設是/,我改了,所以待會也要新增資料夾

Action.java:
public class LoginAction extends ActionSupport {
    private String userName;
    private String userPassword;
    private String displayMessage;

    @Override
    public String execute() throws Exception {
        if (userName.equals("aaa") && userPassword.equals("123")) {
            displayMessage = "login success";
            return SUCCESS;
        } else {
            displayMessage = "login fail";
            return INPUT;
        }
    }

    public String getDisplayMessage() {
        return displayMessage;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }
}
帳號密碼給setter就夠了,顯示訊息給getter就夠了,要全打也行
SUCCESS和INPUT,是繼承ACTION的field,自己打也行,和struts.xml的result name對應好就可以了
login.jsp:
<s:property value="displayMessage"/>
<form action="login.action">
    <input type="text" name="userName" /><br />
    <input type="password" name="userPassword" /><br />
    <input type="reset" />
    <input type="submit" />
</form>
先把s:那一行註解掉,要先可以run才行
要用時還得在上面加<%@ taglib prefix="s" uri="/struts-tags" %>
fail.jsp自己隨便寫
架構圖:

.能看到頁面後,再將s:加回去,這時會出錯,因為struts2的設計必需是從action(因為web.xml設定action)跳轉的
.所以在網址列上最後的login.jsp改成login.action?userName=&userPassword=就可以看到頁面了
.如果頁面的屬性少打會出action的NullPointerException
.如果直接訪問jsp會出「The Struts dispatcher cannot be found.  This is usually caused by using Struts tags without the associated filter. Struts tags are only usable when the request has passed through its servlet filter, which initializes the Struts dispatcher needed for this tag. - [unknown location]」這種錯誤訊息

※伺服器/用戶端跳轉

先將s:註解掉來測
<action name="login" class="login.LoginAction" method="execute">
    <result name="success" type="dispatcher">/struts2/login.jsp</result>
    <result name="input" type="redirect">/struts2/login.jsp</result>
</action>
result多一個屬性type,總共有14種,看這裡
常用的有兩種,和一種servlet已經知道的
.dispatcher:伺器器端跳轉,預設
.redirect:客戶端跳轉,不常用
.redirectAction:跳轉到下一個Action
所以我現在設的是成功就是伺服器端跳轉,失敗就客戶端跳轉
試的結果為伺服器端會變action,用戶端是jsp
其他的我都沒試過