2015年7月5日 星期日

Struts 使用DynaActionForm(Struts二)

DynaActionForm分成要不要validate()兩部分:

※不要validate()

struts-config.xml在HelloWorld的例子改成如下
1.ActionForm刪除,不需要了,就是因為不需要validate,所以不寫
2.struts-config.xml修改
<form-beans>
    <form-bean name="ooo" type="org.apache.struts.action.DynaActionForm"><!--type要改-->
        <!--對應jsp的屬性寫在這-->
        <form-property name="username" type="java.lang.String" />
        <form-property name="password" type="java.lang.String" />
    </form-bean>
</form-beans>
<action-mappings>
    <action path="/xxx" type="controller.LoginAction" name="ooo" scope="request" validate="false">--validate變false,所以input也不要了
        <forward name="success" path="/success.jsp"></forward>
        <forward name="fail" path="/fail.jsp"></forward>
    </action>
</action-mappings>

3.Action要強轉
DynaActionForm laForm = (DynaActionForm) form;
// 和struts-config.xml裡的form-bean,裡面的form property的name和type一樣
String username = (String) laForm.get("username");
String password = (String) laForm.get("password");

※要validate()

1.struts-config.xml修改
<form-beans>
    <!--看起來一樣,但必需繼承DynaActionForm-->
    <form-bean name="ooo" type="controller.LoginActionForm">
        <!--對應jsp的屬性寫在這-->
        <form-property name="username" type="java.lang.String" />
        <form-property name="password" type="java.lang.String" />
    </form-bean>
</form-beans>
<action-mappings>
    <action path="/xxx" type="controller.LoginAction" name="ooo" scope="request" validate="true" input="/index.jsp">
        <forward name="success" path="/success.jsp"></forward>
        <forward name="fail" path="/fail.jsp"></forward>
    </action>
</action-mappings>
    

2.Action要強轉 因為在struts-config.xml已經設定屬性,所以不用寫屬性和setter/getter了
public class LoginActionForm extends DynaActionForm {
    private static final long serialVersionUID = -9011126116308228383L;
    @Override
    public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
        // 還是和struts-config.xml裡的form-bean,裡面的form property的name和type一樣
        String username = (String) this.get("username");
        String password = (String) this.get("password");
        
        ActionErrors errors = new ActionErrors();
        if(!username.equals("aaa")){
            errors.add("user", new ActionMessage("error.actionForm.user"));
        }
        if(!password.equals("bbb")){
            errors.add("pass", new ActionMessage("error.actionForm.pass"));
        }
        return errors;
    }
}

2015年7月3日 星期五

Enum、EnumMap、EnumSet

在1.5之前是這樣子寫的,一個棋類,有三種棋
class Chess {
    public static final int ELEPHANT = 0; // 象棋
    public static final int JUMP = 1; // 跳棋
    public static final int LOOP = 2; // 圍棋

    private int type;

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }
}

用類.屬性當然OK,但顯示出來是數字,不夠直覺; 但因為是傳int,所以傳負的也不會報錯
public class TestEnum {
    public static void main(String[] args) {
        Chess chess = new Chess();
        chess.setType(Chess.ELEPHANT);
        System.out.println(chess.getType());
        chess.setType(-5);
        System.out.println(chess.getType());
    }
}

1.5有了Enum,在java.lang,是個class,用法如下
public enum Chess {
    ELEPHANT, JUMP, LOOP;
}

class ChessType {
    private Chess type;

    public Chess getType() {
        return type;
    }

    public void setType(Chess type) {
        this.type = type;
    }
}

測試類
public class TestEnum {
    public static void main(String[] args) {
        System.out.println(Chess.ELEPHANT);
        ChessType chess = new ChessType();
        chess.setType(Chess.JUMP);// 只能傳Enum
        System.out.println(chess.getType());

        for (Chess chess : Chess.values()) {//取得全部
            System.out.println(chess);//等同chess.name()
            System.out.println(chess.ordinal() + "=" + chess.name());
        }
        
        for (Chess chess : Chess.values()) {
            switchMethod(chess);
        }
    }

    private static void switchMethod(Chess chess) {//中文轉換
        switch (chess) {
            case ELEPHANT: {
                System.out.println("象棋");
                break;
            }
            case JUMP: {
                System.out.println("跳棋");
                break;
            }
            case LOOP: {
                System.out.println("圍棋");
                break;
            }
            default: {
                System.out.println("無法騎");
            }
        }
    }
}

結果:
ELEPHANT
ELEPHANT
0=ELEPHANT
JUMP
1=JUMP
LOOP
2=LOOP
象棋
跳棋
圍棋

※Enum的建構子


public enum Chess {
    ELEPHANT("象棋"), JUMP("跳棋"), LOOP("圍棋");
    Chess(String chessName) {//建構子會自動轉為private,可以不寫private
        this.setChessName(chessName);
    }

    String chessName;

    public String getChessName() {
        return chessName;
    }

    public void setChessName(String chessName) {
        this.chessName = chessName;
    }
}

呼叫建構子沒什麼特別
for (Chess chess : Chess.values()) {
    System.out.println(chess.ordinal() + "=" + chess.name() + "=" + chess.getChessName());
}

結果:
0=ELEPHANT=象棋
1=JUMP=跳棋
2=LOOP=圍棋

※其他


轉換成Enum
Chess chess = Enum.valueOf(Chess.class, "JUMP");//注意大小寫,第二個參數是使用Chess.name的回傳值
chess.setChessName("我跳");
System.out.println(chess.getChessName());

排序
Set<Chess> set = new TreeSet<>();
set.add(Chess.LOOP);
set.add(Chess.JUMP);
set.add(Chess.ELEPHANT);
Iterator<Chess> it = set.iterator();
while(it.hasNext()){
    System.out.println(it.next() + " ");
}

implement interface

interface Msg {
    public String getInfo();
}

public class TestEnum {
    public enum Chess implements Msg {
        ELEPHANT {
            @Override
            public String getInfo() {
                return "象棋";
            }
        },
        JUMP {
            @Override
            public String getInfo() {
                return "跳棋";
            }
        },
        LOOP {
            @Override
            public String getInfo() {
                return "圍棋";
            }
        };
    }

    public static void main(String[] args) {
        for (Chess chess : Chess.values()) {
            System.out.print(chess + "=" + chess.getInfo() + " ");
        }
    }
}

上面的程式碼也可以不implement,用abstract也可以
public enum Chess {
    ELEPHANT {
        @Override
        public String getInfo() {
            return "象棋";
        }
    },
    JUMP {
        @Override
        public String getInfo() {
            return "跳棋";
        }
    },
    LOOP {
        @Override
        public String getInfo() {
            return "圍棋";
        }
    };
    public abstract String getInfo();
}

※使用EnumMap


import java.util.EnumMap;
import java.util.Map;

public class TestEnumMap {
    public enum Chess {
        ELEPHANT, JUMP, LOOP;
    }

    public static void main(String[] args) {
        //Map<Chess, String> map = new TreeMap<>();//使用這行結果也是完全一樣
        Map<Chess, String> map = new EnumMap<>(Chess.class);
        map.put(Chess.ELEPHANT, "象棋");
        map.put(Chess.JUMP, "跳棋");
        map.put(Chess.LOOP, "圍棋");

        // 取key
        for (Chess chess : map.keySet()) {
            System.out.print(chess.name() + " ");
        }
        System.out.println();
        
        // 取value
        for (String value : map.values()) {
            System.out.print(value + " ");
        }
        System.out.println();

        // 取key=value
        for (Chess chess : Chess.values()) {
            System.out.println(chess.name() + "=" + map.get(chess));
        }
    }
}

※使用EnumSet

public class TestEnumSet {
    public enum Chess {
        ELEPHANT, JUMP, LOOP;
    }

    public static void main(String[] args) {
        //allOf:將Enum的值全抓進來
        for(Chess chess:EnumSet.allOf(Chess.class)){
            System.out.println(chess);
        }
        System.out.println();
        //of:只抓特定的Enum
        for(Chess chess:EnumSet.of(Chess.ELEPHANT)){
            System.out.println(chess);
        }
        System.out.println();
        //of:只抓特定的Enum,為overloading
        for(Chess chess:EnumSet.of(Chess.ELEPHANT, Chess.JUMP)){
            System.out.println(chess);
        }
        System.out.println();
        //noneOf:創建一個空的、特定的Enum
        EnumSet<Chess> es = EnumSet.noneOf(Chess.class);
        es.add(Chess.LOOP);
        for(Chess chess:es){
            System.out.println(chess);
        }
        System.out.println();
        //complementOf:取目前已有的Enum的值,然後取反
        for(Chess chess:EnumSet.complementOf(es)){
            System.out.println(chess);
        }
        System.out.println();
        //copyOf:複製Enum
        for(Chess chess:EnumSet.copyOf(es)){
            System.out.println(chess);
        }
        System.out.println();
        //clone:複製Enum
        for(Chess chess:es.clone()){
            System.out.println(chess);
        }
    }
}

2015年7月2日 星期四

讀取國際化資源

讀取國際化資源還蠻常用的,如下有四個properties,分別代表簡中、繁中、美國、什麼都沒有時顯示的
簡中:Morning_zh_CN.properties
msg=\u65e9\u4e0a\u597d,{0}!{1}{2}
繁中:Morning_zh_TW.properties
msg=\u65e9\u5b89,{0}!{1}{2}
美國:Morning_en_US.properties
msg=Good Morning,{0}!{1}{2}
預設:Morning.properties
msg=original{0}{1}{2}

_xx_xx:要真的有這個國家,不然找不到
\uxxxx:因為properties不能打ascii以外的字,應該吧,反正中文不行,所以有一個工具在java安裝資料夾的bin,裡面有一支native2ascii.exe,點兩下打中文就會出現了,按右鍵即複製,貼到properties裡。
{number}:可無限遞增,從程式碼傳過去的參數
P.S 但我的環境是繁中,叫出native2ascii.exe,我打簡體或日文都會出現?,所以只能打繁中。
我找到一個小方法,在執行打charmap會叫出字元對應表,如下

勾進階檢視,會出現更多選項,字元集選簡中,分組方式可以更小範圍搜索,按隨便一個字,如我按的這個「蝇」左下角就有U+8747,把+去掉,最前面加「\」,改個小寫比較像,這樣子就完成了,只是要特定的字不知有沒有辦法,雖然有搜尋目標的框,可是好像沒用。

找到一個更好的方法了,切換到Properties活頁標籤,直接打在裡面,然後切回Source就自動會轉換了,所以可以不用native2ascii.exe了


寫個程式來測試一下
final String prop = "Morning";
final String res = "msg";
ResourceBundle simple = ResourceBundle.getBundle(prop, Locale.PRC);
ResourceBundle traditional = ResourceBundle.getBundle(prop, Locale.TAIWAN);
ResourceBundle english = ResourceBundle.getBundle(prop, Locale.US);
System.out.println("简中:" + simple.getString(res));
System.out.println("繁中:" + traditional.getString(res));
System.out.println("English:" + english.getString(res));
System.out.println();
System.out.println(MessageFormat.format(simple.getString(res), "小龙哥", "测试1", "测试2"));
System.out.println(MessageFormat.format(traditional.getString(res), "小龍哥", "測試1", "測試2"));
System.out.println(MessageFormat.format(english.getString(res), "small dragon guy", "test1", "test2"));

結果:
简中:早上好,{0}!{1}{2}
繁中:早安,{0}!{1}{2}
English:Good Morning,{0}!{1}{2}

早上好,小龙哥!测试1测试2
早安,小龍哥!測試1測試2
Good Morning,small dragon guy!test1test2

※1.第一行getBundle的第一個參數要打properties的名稱,不包括_後面的,副檔名也不用,不然會報錯
2.第五行,若getString不到也會報錯
3.MessageFormat.format第二個參數是…,所以可以一直加,對應{0}{1}…,如果不夠會把{0}{1}這些顯示出來
4.假如zh_TW打錯了,譬如zh_TM,那就代表抓不到,會抓到預設的Morning.properties,如果連Morning.properties檔都沒有,就會報錯,所以這個檔不是必要的
5.xx_xx這個東西可以來這查

複製物件

1.Object 有個clone() 方法,可以達到複製的目的
但還要implements Cloneable,不然會報CloneNotSupportedException

2.可以將clone()的protected 改成 public,也可將回傳的 Object 改成指定的物件

3.原形模式


※無集合

public class ClassPrototype implements Cloneable {
    private String xxx;
    private Integer ooo;
    
    // setter/getter...
    
    public ClassPrototype() {
        System.out.print("default constructor!");
    }
    
    @Override
    protected ClassPrototype clone() throws CloneNotSupportedException {
        return (ClassPrototype) super.clone();
    }
}




ClassPrototype prototype = new ClassPrototype();
prototype.setXxx("banana");
prototype.setOoo(20);
    
ClassPrototype copyPrototype;
try {
    copyPrototype = prototype.clone();
    System.out.println(copyPrototype.getXxx());
    System.out.println(copyPrototype.getOoo());
    
    copyPrototype.setXxx("apple");
    System.out.println(prototype.getXxx());
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

※結果:
default constructor!banana
20
banana

※第一行會呼叫建構子,但複製的時候不會

※基本型態和 String,用 == 是false,沒有什麼問題



※淺複製、深複製

假設複製的物件是傳集合,
複製好的物件會影響到原本的物件,就是淺複製;不會影響的就是深複製


※深複製


public class ClassPrototype implements Cloneable {
    private String xxx;
    private List<String> list;
    // setter/getter...
    
    public ClassPrototype() {
        System.out.print("default constructor!");
    }
    
    @Override
    protected ClassPrototype clone() throws CloneNotSupportedException {
        return (ClassPrototype) super.clone();
    }
    
    public static void main(String[] args) {
        ClassPrototype prototype = new ClassPrototype();
        prototype.setXxx("banana");
        prototype.setList(Arrays.asList("a", "b"));
    
        ClassPrototype copyPrototype;
        try {
            copyPrototype = prototype.clone();
            System.out.println(copyPrototype.getXxx());
            System.out.println(copyPrototype.getList());
   
            copyPrototype.setList(Arrays.asList("c", "d"));
            System.out.println(copyPrototype.getList());
            System.out.println(prototype.getList());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}




※淺複製

public class ClassPrototype implements Cloneable {
    private ArrayList<String> list;
    // setter/getter...
    
    public ClassPrototype() {
        System.out.println("default constructor!");
    }
    
    @SuppressWarnings("unchecked")
    @Override
    protected ClassPrototype clone() throws CloneNotSupportedException {
        ClassPrototype cp = (ClassPrototype) super.clone();
        // cp.list = (ArrayList<String>) list.clone();
        return cp;
    }
    
    public static void main(String[] args) {
        ClassPrototype prototype = new ClassPrototype();
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        prototype.setList(list);
    
        ClassPrototype copyPrototype;
        try {
            copyPrototype = prototype.clone();
            System.out.println(copyPrototype.getList());
    
            copyPrototype.getList().add("c");
            System.out.println(copyPrototype.getList());
            System.out.println(prototype.getList());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}


※想改成深複製,將註解打開即可,但因為 List 沒有實作 Cloneable,所以沒有 clone 方法可用,所以改了 ArrayList;也可用 cp.list.list = (ArrayList<String>) ((ArrayList<String>) list).clone();

※註解還沒打開時,使用 == 判斷會發現是 true

注意:用 add 是正常的,但如果用 copyPrototype.setList(xxx),或將 copyPrototype new 一個全新的 copyPrototype,用 == 都會是 false,類似 call by reference 的問題
但將 copyPrototype = null ,然後在去.方法居然沒報錯,很奇怪


※自定物件

自定的和上面的集合都一樣,如下

public class ClassPrototype implements Cloneable {
    private Book book;
    // setter/getter...
    
    @Override
    protected ClassPrototype clone() throws CloneNotSupportedException {
        ClassPrototype cp = (ClassPrototype) super.clone();
        cp.book = book.clone();
        return cp;
    }
    
    public static void main(String[] args) {
        ClassPrototype prototype = new ClassPrototype();
        Book book = new Book();
        book.setName("abc");
        prototype.setBook(book);
    
        ClassPrototype copyPrototype;
        try {
            copyPrototype = prototype.clone();
            System.out.println(copyPrototype.getBook().getName());
    
            // 1.setter方式是OK的
            // Book b = new Book();
            // b.setName("def");
            // copyPrototype.setBook(b);
    
            // 2.getter再 setter 也沒問題
            copyPrototype.getBook().setName("def");
            System.out.println(copyPrototype.getBook().getName());
            System.out.println(prototype.getBook().getName());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}
    
class Book implements Cloneable {
    private String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    protected Book clone() throws CloneNotSupportedException {
        return (Book) super.clone();
    }
}


Struts Hello World(Struts一)

首先了解一下
ActinServlet:Struts的Controller
struts-config.xml:struts主要的設定檔
ActionForm:負責處理Controller中接收、驗證、轉換資料的功能
Action:處理Controller呼叫Model和根據Model執行結果導向下一個View的功能

1.放jar,注意jstl.jar和standard.jar版本較舊,用maven下載是個不錯的選擇
  我是將 jstl-1.0.2.jar 換 jstl-1.2.jar;standard-1.0.6.jar 換 standard-1.1.2.jar
2.設定web.xml-->從blank去copy
3.加入struts-config.xml-->放在WEB-INF裡,從blank去copy
4.加入properties(放在/WEB-INF/classes,也就是src下),並設定struts-config.xml,從blank去copy

功能:
頁面如果不輸入,就會出現錯誤訊息
如果帳號是aaa而且密碼是111就跳成功頁,不然就跳失敗頁(模擬資料庫)

struts-config.xml
<struts-config>
    <form-beans>
        <form-bean name="ooo" type="controller.LoginActionForm">
            <form-property name="username" type="java.lang.String" />
            <form-property name="password" type="java.lang.String" />
        </form-bean>
    </form-beans>

    <action-mappings>
        <action path="/xxx" type="controller.LoginAction" name="ooo"
            scope="request" validate="true" input="/index.jsp">
            <forward name="success" path="/s.jsp" />
            <forward name="fail" path="/f.jsp" />
        </action>
    </action-mappings>

    <message-resources parameter="MessageResources" />
</struts-config>
path:和html的.do一樣,但不用.do
type:action的家
name:和form-bean name一樣
scope:就是scope
validate:是否驗證
input:錯誤跳轉

ActionForm.java
public class LoginActionForm extends ActionForm {
    private String username;
    private String password;

    //setter/getter...

    @Override
    public ActionErrors validate(ActionMapping mapping,
            HttpServletRequest request) {
        ActionErrors errors = new ActionErrors();
        System.out.println("username=" + this.getUsername());
        System.out.println("password=" + this.getPassword());

        if (this.getUsername().trim().isEmpty()) {
            errors.add("user", new ActionMessage("username"));
        }
        if (this.getPassword().trim().isEmpty()) {
            errors.add("pass", new ActionMessage("password"));
        }
        return errors;
    }
}

Action.java
public class LoginAction extends Action {
    @Override
    public ActionForward execute(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        LoginActionForm laf = (LoginActionForm) form;
        
        if(laf.getUsername().trim().equals("aaa") && laf.getPassword().trim().equals("111")){
            return mapping.findForward("success");
        } else {
            return mapping.findForward("fail");
        }
    }
}

index.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="html" uri="http://struts.apache.org/tags-html" %>
<html:errors/>
<form action="<c:url value="/xxx.do"/>">
    帳:<input type="text" name="username" /><html:errors property="user" />
    密:<input type="password" name="password" /><html:errors property="pass" />
    <input type="submit" value="送出" />
    <input type="reset" value="重置" />
</form>

s.jsp
<h1>Login Success</h1>

f.jsp
<h1>Login Fail</h1>

MessageResources.properties
username=username can't empty
password=password can't empty 


※如果Action要顯示錯誤訊息也可以

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

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

struts-config.xml修改fail路徑
<forward name="fail" path="/index.jsp" />

properties也增加一行
actionUP=please Enter legal username and password!

※注意:如果有「No bean specified」錯誤訊息,通常都是formbean有集合,此時集合要實體化,如:
private List<Student> students; --> private List<Student> students = new ArrayList<>();
如果Student類別還有集合,也要實體化


※DispatchAction

是 Action 的子類
controller 若繼承 DispatchAction 這個類,然後裡面寫很多方法,然後 struts-config.xml 的 action標籤,有個 parameter 屬性,通常喜歡設定成 method,這樣子就可以用 xxx.do?method=方法名,此方法名會對應你寫的方法名

注意:
.4 個參數都不能變,否則會 500;而回傳值我寫 void 是可以的
.如果在此類別覆寫 execute 方法,那執行時會呼叫到 execute 方法,其他都不會呼叫
.如果繼承一個父類,此父類繼承 DispatchAction  且覆寫 execute 方法,那執行時會先呼叫 execute 方法,然後再呼叫 method=xxx 的方法
.GET、POST 都可以

2015年7月1日 星期三

使用tomcat連資料庫

tomcat選自己的資料庫,1~3都copy起來,1.放到META-INF/context.xml,這裡有講。
整合如下:以oracle為例
context.xml
<Resource name="jdbc/myoracle" auth="Container"
              type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver"
              url="jdbc:oracle:thin:@127.0.0.1:1521:mysid"
              username="username" password="password" maxTotal="20" maxIdle="10"
              maxWaitMillis="-1"/>
web.xml
<resource-ref>
    <description>Oracle Datasource example</description>
    <res-ref-name>jdbc/myoracle</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
</resource-ref>
java
Context initContext = new InitialContext();
Context envContext  = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/myoracle");
Connection conn = ds.getConnection();

Mybatis3.x HelloWorld(Mybatis3.x一)


<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

※還要有資料庫驅動程式,oracle 在這裡,可用 SELECT * FROM v$version; 查版本


mybatis和Hibernate都是連資料庫轉換成物件的東東
1.所以一開始先將DB的驅動程式和mybatis的jar放到lib裡
2.寫一隻和DB相關的properties,名稱隨便取,我是叫jdbc.properties,如下
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
username=your account
password=your password

3.寫一隻mybatis-config.xml,從這邊copy後,再修改 如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="jdbc.properties"></properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${driver}" />
                <property name="url" value="${url}" />
                <property name="username" value="${username}" />
                <property name="password" value="${password}" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="org/mybatis/model/Dept.xml" />
    </mappers>
</configuration>

※environments 裡可以寫很多的 environment,譬如 oracle、mysql…等資料庫,改 default 的字串,就可以輕鬆切換

※我知道加這行properties resource,是因為這裡有講,所以也可以將properties的內容寫在裡面或混用

※4.第3步裡面有mapper resource的xml,所以就寫一隻xml吧!檔案名稱可和DB的表名稱不一樣,Dept.xml

<?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="org.mybatis.model.Dept">
        select * from dept where deptno = #{deptNo}
    </select>
</mapper>

parameterType為傳進去的參數型態是什麼,給#{}接收; resultType為回傳的型態是什麼

5.resultType會連到java,寫一下唄
package org.mybatis.model;
public class Dept {
    private int deptNo;
    private String dName;
    private String loc;

    public int getDeptNo() {
        return deptNo;
    }

    public void setDeptNo(int deptNo) {
        this.deptNo = deptNo;
    }

    public String getdName() {
        return dName;
    }

    public void setdName(String dName) {
        this.dName = dName;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }
}

6.寫一支測試類來測一下,從這裡copy後,稍加修改
public static void main(String[] args) {
    InputStream is = null;
    SqlSession sqlSession = null;
    try {
        is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder()
                .build(is);
        sqlSession = factory.openSession();

        Dept dept = sqlSession.selectOne("Dept.getDeptById", 40);
        System.out.println("deptNo=" + dept.getDeptNo());
        System.out.println("dName=" + dept.getdName());
        System.out.println("loc=" + dept.getLoc());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        sqlSession.close();
        try {
            if (is != null) {
                is.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

※Dept.getDeptById為namespace.id

※如果 java bean 的名稱和資料庫的名稱不一樣,可以用 as 或者使用 <resultMap>,後續會說明



※介面編程

以上是舊的寫法,現在用的是介面來寫,首先寫一個介面,介面定義一些方法,然後 xml 的namespace 寫 interface 的全類名,id 為方法名,這樣就會自動對應了(但不能用overloading),如下:


interface

public Dept getDeptById(int id);
    
public Dept getDeptByIdAndLoc(@Param("id") int id, @Param("loc") String loc);
    
public Dept getDeptByList(@Param("ids") List<Integer> ids);
    
public Dept getDeptByMap(Map<String, Object> map);
    
public List<Dept> getListByName(String name);
    
public Map<String, Object> getMapById(int id);
    
@MapKey("deptNo")
public Map<Integer, Dept> getMapByName(String name);



xml

<select id="getDeptById" resultType="mp.bean.Dept">
    select * from dept where
    deptno = #{deptNo}
</select>
    
<select id="getDeptByIdAndLoc" resultType="mp.bean.Dept">
    select * from dept
    where deptno = #{id}
    and loc = #{loc}
    
<!--         where deptno = #{param1} -->
<!--         and loc = #{param2} -->
    
<!--         where deptno = #{arg0} -->
<!--         and loc = #{arg1} -->
</select>
    
<select id="getDeptByList" resultType="mp.bean.Dept">
    select * from dept
    where deptno = #{ids[0]} <!-- #{list[0]} -->
</select>
    
<select id="getDeptByMap" resultType="mp.bean.Dept">
    select * from dept
    where deptno = #{xxx}
    and loc = #{loc}
</select>
    
<select id="getListByName" resultType="mp.bean.Dept">
    select * from dept
    where dname like #{name}
</select>
    
<select id="getMapById" resultType="map">
    select * from dept
    where deptno = #{id}
</select>
    
<select id="getMapByName" resultType="mp.bean.Dept">
    select * from dept
    where dname like #{name}
</select>

※回傳值寫 map、list 會幫我們對應到 java 常用的類別或介面了,這是因為 mybatis 已經有內鍵的別名了,還有很多,可參考官網

測試類

// Dept dept = sqlSession.selectOne("Dept.getDeptById", 40);
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
            
/*
Dept dept = mapper.getDeptById(40);
System.out.println("dept=" + dept);
System.out.println("deptNo=" + dept.getDeptNo());
System.out.println("dName=" + dept.getdName());
System.out.println("loc=" + dept.getLoc());
*/

/*
Dept dept = mapper.getDeptByIdAndLoc(40, "BOSTON");
System.out.println("dept=" + dept);
*/

/*
List<Integer> list = new ArrayList<>();
list.add(40);
Dept dept = mapper.getDeptByList(list);
System.out.println("dept=" + dept);
*/

/*
Map<String, Object> map = new HashMap<>();
map.put("xxx", 40);
map.put("loc", "BOSTON");
Dept dept = mapper.getDeptByMap(map);
System.out.println("dept=" + dept);
*/

/*
List<Dept> listByName = mapper.getListByName("%O%");
System.out.println(listByName);//找不到不是null,是空的 list
listByName.forEach((dept) -> {
    System.out.println(dept.getdName());
});
*/

/*
Map<String, Object> mapById = mapper.getMapById(40);
System.out.println(mapById);// 找不到為null
*/

Map<Integer, Dept> mapByName = mapper.getMapByName("%O%");
System.out.println(mapByName);// 找不到不是null,是空的 map

※注意:
1.parameterType 可不寫,mybatis 會用TypeHandler 自動判斷

2.傳一個參數時,#{} 裡面可以隨便寫
傳多個參數時,第一個參數可寫 args0 或 param1,第二個參數可寫 args1 或 param2…依此類推,但如果配合 @Param 時,只能寫 @Param 的值和 paramX,就不能寫 argsX 了
錯誤訊息有提示 Available parameters are [arg1, arg0, param1, param2]

3.回傳值是 List 或 Set 時,查詢不到返回的是空 list,不是 null

4.回傳值是 Map 時,如果泛型沒有 java bean 查詢不到時會回傳 null;但如果有 java bean,會回傳空 map

5.List、Map 裡的泛型有 java bean,在 xml 裡的 resultType 也要寫 java bean 才可以,否則List 會出 java.lang.UnsupportedOperationException 的錯; Map 只有一筆

6.mybatis-config.xml 的 mappers 標籤的 mapper resource 除了可以一次設定一個 xml 路徑外,還可以用掃瞄包的方式 package name="mp.bean.dao" 一次就抓很多的 xml 了,但要注意 xml 必須放在和 interface 同一層目錄(下一層也不行)且同名才行
或者在 interface 使用 @Mapper 也行,但因為沒有 SQL了,還得加上類似 @Select 的註解即可

7.如果參數是物件,xml 裡直接寫屬性名即可,但如果加了 @Param,那就只能用 param1或@Param 裡面的字串



※在 Console 印 SQL

mybatis 支援很多 log,可看官網,這裡以 log4j2 為例
1.取得 jar 檔,推薦用 maven、gradle 等
2. 寫 log4j2.xml 放在 classpath 目錄下,然後將 Root level 放 DEBUG 以上層級
以上兩個可參考我 log4j2 的文章,其中第 2 步有很多做法,我是用官網自動設定的第9招

3.mybatis 設定 <setting name="logImpl" value="LOG4J2" /> 即可
typeAliases 使用 package name 時會有亂碼,不知道為什麼,不過不影響 SQL 就是了
有興趣的可以看源碼的 DefaultVFS 的 list 方法,第二個 for 迴圈

setting 的設定很多,可看官網