2015年7月28日 星期二

Mybatis 的 Annotation Mybatis3.x(九)

※事前規劃

CREATE TABLE CHESS_TABLE(
    CID  NUMBER,
    NAME  VARCHAR(20),
    PRODUCT_DATE DATE,
    ATTENTION VARCHAR(20),
    CONSTRAINT CHESS_TABLE_PK PRIMARY KEY(CID)
);
COMMENT ON TABLE CHESS_TABLE IS '棋表';
COMMENT ON COLUMN CHESS_TABLE.CID IS '棋的ID';
COMMENT ON COLUMN CHESS_TABLE.NAME IS '棋的名稱';
COMMENT ON COLUMN CHESS_TABLE.PRODUCT_DATE IS '製造日期';
COMMENT ON COLUMN CHESS_TABLE.ATTENTION IS '注意事項';
CREATE SEQUENCE CHESS_TABLE_SEQUENCE;

※介面

.java bean是一定要的,沒什麼特別就不show了
.以下是介面,以往都要有DaoImpl的實作介面的東西,但因為有Annotation,所以不用了
.注意有底線的欄位,還是要用別名,不然會查到null
public interface IChessTableDAO {
    @Insert("INSERT INTO CHESS_TABLE(CID, NAME, PRODUCT_DATE, ATTENTION) VALUES (#{cid}, #{name}, #{productDate}, #{attention})")
    @SelectKey(before = true, keyProperty = "cid", resultType = java.lang.Integer.class, statement = "SELECT CHESS_TABLE_SEQUENCE.NEXTVAL FROM DUAL")
    public boolean insert(ChessTable chessTable);

    @Delete("DELETE FROM CHESS_TABLE WHERE CID = #{xxx}")
    public boolean delete(Integer cid);

    @Update("UPDATE CHESS_TABLE SET NAME = #{name}, PRODUCT_DATE = #{productDate}, ATTENTION = #{attention} WHERE CID = #{cid}")
    public boolean update(ChessTable chessTable);

    @Select("SELECT CID, NAME, PRODUCT_DATE PRODUCTDATE, ATTENTION FROM CHESS_TABLE WHERE CID = #{xxx}")
    public ChessTable findById(Integer cid);

    @Select("SELECT CID, NAME, PRODUCT_DATE PRODUCTDATE, ATTENTION FROM CHESS_TABLE")
    public List<ChessTable> findAll();

    @Select("SELECT COUNT(CID) FROM CHESS_TABLE WHERE ${ooo} LIKE #{xxx}")
    public int count(@Param("ooo") String column, @Param("xxx") String keyWord);
}

※測試類

#{xxx}和${xxx}就差在,有#的會幫我們轉換型態,譬如傳String,它會幫我們轉成「'xxx'」,而$就不會,可以利用這點debug
SqlSession sqlSession = MybatisUtil.getSession();
ChessTable ct = new ChessTable();
ct.setName("孔明棋");
ct.setProductDate(new Date());
ct.setAttention("不可食用!");

IChessTableDAO dao = sqlSession.getMapper(IChessTableDAO.class);

// 新增
// System.out.println(dao.insert(ct));
// System.out.println(ct.getCid());

// 修改
// ct.setName("黑白棋");
// ct.setCid(2);
// System.out.println(dao.update(ct));

// 查一筆
// ChessTable chessTable = dao.findById(2);
// System.out.println(chessTable.getName());
// System.out.println(chessTable.getProductDate());
// System.out.println(chessTable.getAttention());

// 查全部
// List<ChessTable> lct = dao.findAll();
// for(ChessTable l:lct){
// System.out.println(l.getCid());
// System.out.println(l.getName());
// System.out.println(l.getProductDate());
// System.out.println(l.getAttention());
// System.out.println("----------");
// }

// 查關鍵字
// System.out.println(dao.count("NAME", "%白%"));

// 刪除
System.out.println(dao.delete(2));

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

※Annotaion 加動態標籤

@Select({
	"<script>",
		"SELECT * FROM table where xxx in",
		"<foreach collection='ooo' item='id' open='(' separator=',' close=')'>",
		"#{id}",
		"</foreach>",
	"</script>"
})
List<String> getXxx(@Param("ooo") List<Long> ooo);

※Mapper註冊

執行到getMapper那一行會出「Type xxx is not known to the MapperRegistry.」的錯,之前是用xml設定,會去設定檔抓<mappers>裡的設定,所以這時要設定,有四種方式

以下是Util的設定兩者取其一,看是針對package或其中一隻設定,Util全貌可看Mybatis3.x(二)
reader = Resources.getResourceAsReader(CONFIG_FILE_LOCATION);
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
//這是在static區塊裡,上兩行本來就有,下兩行擇其一
sessionFactory.getConfiguration().addMapper(IChessTableDAO.class);
// sessionFactory.getConfiguration().addMappers("org.mybatis.dao");

.如果不想寫在Util也可寫在設定檔,還是<mapper>,一樣是針對package或其中一隻
.是mapper class喔!不是之前的mapper resource
<configuration>
    <properties resource="jdbc.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>
<!--     <package name="org.mybatis.dao"/> -->
        <mapper class="org.mybatis.dao.IChessTableDAO"/>
    </mappers>
</configuration>

2015年7月27日 星期一

雙向多對多關聯 Mybatis3.x(八)

多對多關聯可以用一張中介的table做為兩個一對多的關聯來完成
譬如一個人可以住很多房子,房子也可以很多人住

※事前規劃

DROP TABLE PERSON_HOME;
DROP TABLE PERSON;
DROP TABLE HOME;

CREATE TABLE PERSON(
 PID  NUMBER,
 NAME  VARCHAR(30) NOT NULL ,
 CONSTRAINT PERSON_PK PRIMARY KEY(PID)
);
COMMENT ON TABLE PERSON IS '人表';
COMMENT ON COLUMN PERSON.PID IS '人id';
COMMENT ON COLUMN PERSON.NAME IS '姓名';

CREATE TABLE HOME(
 HID  NUMBER,
 ADDRESS_NO NUMBER NOT NULL ,
 CONSTRAINT HOME_PK PRIMARY KEY(HID)
);
COMMENT ON TABLE HOME IS '房子表';
COMMENT ON COLUMN HOME.HID IS '房子id';
COMMENT ON COLUMN HOME.ADDRESS_NO IS '房子號碼';

CREATE TABLE PERSON_HOME(
 PID NUMBER,
 HID NUMBER,
 CONSTRAINT PERSON_HOME_PID_FK FOREIGN KEY(PID) REFERENCES PERSON(PID) ON DELETE CASCADE,
 CONSTRAINT PERSON_HOME_HID_FK FOREIGN KEY(HID) REFERENCES HOME(HID) ON DELETE CASCADE 
);
COMMENT ON TABLE PERSON_HOME IS '人和房子的中介表';
COMMENT ON COLUMN PERSON_HOME.PID IS '人的id';
COMMENT ON COLUMN PERSON_HOME.HID IS '房子的id';

INSERT INTO PERSON(PID, NAME) VALUES (111, 'john');
INSERT INTO PERSON(PID, NAME) VALUES (222, 'apple');
INSERT INTO PERSON(PID, NAME) VALUES (333, 'tom');

INSERT INTO HOME(HID, ADDRESS_NO) VALUES(999, 555);
INSERT INTO HOME(HID, ADDRESS_NO) VALUES(998, 556);
INSERT INTO HOME(HID, ADDRESS_NO) VALUES(997, 557);
INSERT INTO HOME(HID, ADDRESS_NO) VALUES(996, 558);
INSERT INTO HOME(HID, ADDRESS_NO) VALUES(995, 559);
INSERT INTO HOME(HID, ADDRESS_NO) VALUES(994, 560);
INSERT INTO HOME(HID, ADDRESS_NO) VALUES(993, 561);
INSERT INTO HOME(HID, ADDRESS_NO) VALUES(992, 562);

INSERT INTO PERSON_HOME(PID, HID) VALUES(111, 999);
INSERT INTO PERSON_HOME(PID, HID) VALUES(111, 998);
INSERT INTO PERSON_HOME(PID, HID) VALUES(111, 997);
INSERT INTO PERSON_HOME(PID, HID) VALUES(111, 996);
INSERT INTO PERSON_HOME(PID, HID) VALUES(111, 995);
INSERT INTO PERSON_HOME(PID, HID) VALUES(111, 994);
INSERT INTO PERSON_HOME(PID, HID) VALUES(111, 993);
INSERT INTO PERSON_HOME(PID, HID) VALUES(111, 992);
INSERT INTO PERSON_HOME(PID, HID) VALUES(222, 997);
INSERT INTO PERSON_HOME(PID, HID) VALUES(222, 996);
INSERT INTO PERSON_HOME(PID, HID) VALUES(222, 995);
INSERT INTO PERSON_HOME(PID, HID) VALUES(222, 994);
INSERT INTO PERSON_HOME(PID, HID) VALUES(333, 995);
INSERT INTO PERSON_HOME(PID, HID) VALUES(333, 994);
INSERT INTO PERSON_HOME(PID, HID) VALUES(333, 993);
INSERT INTO PERSON_HOME(PID, HID) VALUES(333, 992);

※java檔

兩隻java分別對應到對方,想成一對多,中介的就基本的而已:
public class Person {
    private Integer pid;
    private String name;
    private List<Home> homes;
    //setter/getter...
}

public class Home {
    private Integer hid;
    private Integer addressNo;
    private List<Person> persons;
    //setter/getter...
}

public class PersonHome {
    private Integer pid;
    private Integer hid;
    //setter/getter...
}

※xml設定

Person.xml
<mapper namespace="ooo.Person">
    <sql id="column">
        PID, NAME
    </sql>

    <resultMap type="Person" id="PersonInterface">
        <id property="pid" column="PID" />
        <result property="name" column="NAME" />
    </resultMap>

    <resultMap type="Person" id="PersonHomeInterface" extends="PersonInterface">
        <collection property="homes" javaType="java.util.List"
            ofType="Home" resultMap="xxx.Home.HomeInterface" />
    </resultMap>

    <select id="findPersonHome" parameterType="java.lang.Integer"
        resultMap="PersonHomeInterface">
        SELECT
            P.PID, P.NAME, H.HID, H.ADDRESS_NO
        FROM
            PERSON P, HOME H, PERSON_HOME PH
        <where>
                PH.PID = P.PID
            AND PH.HID = H.HID
            AND P.PID = #{xxx}
        </where>
    </select>
</mapper>

Home.xml
<mapper namespace="xxx.Home">
    <sql id="column">
        HID, ADDRESS_NO
    </sql>
    <resultMap type="Home" id="HomeInterface">
        <id property="hid" column="HID" />
        <result property="addressNo" column="ADDRESS_NO" />
    </resultMap>

    <resultMap type="Home" id="HomePersonInterface" extends="HomeInterface">
        <collection property="persons" javaType="java.util.List"
            ofType="Person" resultMap="ooo.Person.PersonInterface" />
    </resultMap>

    <select id="findHomePerson" parameterType="java.lang.Integer"
        resultMap="HomePersonInterface">
        SELECT
            P.PID, P.NAME, H.HID, H.ADDRESS_NO
        FROM
            PERSON P, HOME H, PERSON_HOME PH
        <where>
                PH.PID = P.PID
            AND PH.HID = H.HID
            AND H.HID = #{ooo}
        </where>
    </select>
</mapper>

※java檔測試:

SqlSession sqlSession = MyBatisSessionFactory.getSession();
System.out.println("*****一個人住很多房子*****");
Person person = sqlSession.selectOne("ooo.Person.findPersonHome", 222);
System.out.println(person.getPid());
System.out.println(person.getName());
System.out.println("------");

List<Home> lh = person.getHomes();
for(Home h : lh){
    System.out.println(h.getHid());
    System.out.println(h.getAddressNo());
    System.out.println("======");
}

System.out.println("*****一間房子有很多人住*****");
Home home = sqlSession.selectOne("xxx.Home.findHomePerson", 996);
System.out.println(home.getHid());
System.out.println(home.getAddressNo());
System.out.println("------");

List<Person> lp = home.getPersons();
for(Person p : lp){
    System.out.println(p.getPid());
    System.out.println(p.getName());
    System.out.println("======");
}
MyBatisSessionFactory.closeSession();

結果:
*****一個人住很多房子*****
222
apple
------
997
557
======
996
558
======
995
559
======
994
560
======
*****一間房子有很多人住*****
996
558
------
111
john
======
222
apple
======


apple住了557、558、559、560號,四間房子
住址558號的房子住了john和apple

2015年7月25日 星期六

雙向一對多關聯 Mybatis3.x(七)

※事前規劃

一個部門有很多員工,多個員工屬於一個部門
DROP TABLE DEPARTMENT;
DROP TABLE EMPLOYEE;
DROP SEQUENCE EMPLOYEE_SEQUENCE;

CREATE TABLE DEPART(
    DEPTNO NUMBER(9),
    DNAME  VARCHAR(20),
    CONSTRAINT DEPART_PK PRIMARY KEY(DEPTNO)
);
COMMENT ON TABLE DEPART IS '部門表';
COMMENT ON COLUMN DEPART.DEPTNO IS '部門代號';
COMMENT ON COLUMN DEPART.DNAME IS '部門名稱';

CREATE TABLE EMPLOYEE(
    EMPNO     NUMBER(9),
    ENAME     VARCHAR(10),
    DEPTNO    NUMBER(9),
    CONSTRAINT EMPLOYEE_PK PRIMARY KEY(EMPNO),
    CONSTRAINT EMPLOYEE_FK FOREIGN KEY(DEPTNO) REFERENCES DEPART(DEPTNO) ON DELETE CASCADE
);
COMMENT ON TABLE EMPLOYEE IS '員工表';
COMMENT ON COLUMN EMPLOYEE.EMPNO IS '員工編號';
COMMENT ON COLUMN EMPLOYEE.ENAME IS '員工姓名';
COMMENT ON COLUMN EMPLOYEE.DEPTNO IS '部門代號';

CREATE SEQUENCE EMPLOYEE_SEQUENCE;

INSERT INTO DEPART VALUES(10, '財務部');
INSERT INTO DEPART VALUES(20, '研發部');
INSERT INTO DEPART VALUES(30, '業務部');
INSERT INTO DEPART VALUES(40, '生管部');

INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'king', 10);
INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'blake', 30);
INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'clark', 10);
INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'jones', 20);

INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'martin', 30);
INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'allen', 30);
INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'turner', 30);
INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'james', 30);
INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'ward', 30);

INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'ford', 20);
INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'smith', 20);
INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'scott', 20);
INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'adams', 20);
INSERT INTO EMPLOYEE VALUES(EMPLOYEE_SEQUENCE.NEXTVAL, 'miller', 20);

※開始戰鬥

兩張類也加一下,一對多可用List或Set
public class Department {
    private Integer deptno;
    private String dname;
    private Set employees;
    //setter/getter...
}

public class Employee {
    private Integer empno;
    private String ename;
    private Integer deptno;
    private Department department;
    //setter/getter...
}

Employee.xml是多對一,所以要設定「一」
<mapper namespace="Emp">
    <sql id="column">
        EMPNO, ENAME, DEPTNO
    </sql>

    <resultMap type="Employee" id="EmpInterface">
        <id property="empno" column="EMPNO" />
        <result property="ename" column="ENAME" />
        <result property="deptno" column="DEPTNO" />

        <!-- 多個員工有「一」個部門 -->
        <association property="department" column="DEPTNO"
            javaType="Department" select="Dept.findById" />
        <!-- resultMap="Dept.DeptInterface" /> 用這個非關聯字段會null,和select擇其一-->
    </resultMap>

    <select id="findAllByDepartment" resultMap="EmpInterface">
        SELECT <include refid="column" />
        FROM EMPLOYEE
        <where>
            DEPTNO = #{ooxx}
        </where>
    </select>

    <select id="findById" parameterType="java.lang.Integer"
        resultMap="EmpInterface">
        SELECT <include refid="column" />
        FROM EMPLOYEE
        <where>
            EMPNO = #{ooo}
        </where>
    </select>
</mapper>

Department.xml是一對多,所以要設定「多」,這是一對一沒有的
javaType="java.util.Set" ofType="Employee",其實就是Set<Employee>
<mapper namespace="Dept">
    <sql id="column">
        DEPTNO, DNAME
    </sql>

    <resultMap type="Department" id="DeptInterface">
        <id property="deptno" column="DEPTNO" />
        <result property="dname" column="DNAME" />

        <!-- 一個部門有很「多」員工 -->
        <collection property="employees" column="DEPTNO" javaType="java.util.Set"
            ofType="Employee" select="Emp.findAllByDepartment" />
    </resultMap>

    <select id="findById" parameterType="java.lang.Integer"
        resultMap="DeptInterface">
        SELECT <include refid="column" />
        FROM DEPARTMENT
        <where>
            DEPTNO = #{xxx}
        </where>
    </select>
</mapper>

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

System.out.println("--------------多對一測試--------------");
Employee e = sqlSession.selectOne("Emp.findById", 2);
System.out.println(e.getEmpno());
System.out.println(e.getEname());
System.out.println(e.getDeptno());

// 如果Employee.xml的resultMap的association用resultMap,非關聯字段會null;
Department dept = e.getDepartment();
System.out.println(dept.getDeptno());
System.out.println(dept.getDname() + "\r\n");//非關聯字段

System.out.println("--------------一對多測試--------------");
Department d = sqlSession.selectOne("Dept.findById", 20);
System.out.println(d.getDeptno());
System.out.println(d.getDname() + "\r\n");

Set<Employee> l = d.getEmployees();
for (Employee emp : l) {
    System.out.println(emp.getEmpno());
    System.out.println(emp.getEname());
    System.out.println(emp.getDeptno());
    System.out.println("================================");
}

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




※一對多關聯的兩種方式


<resultMap type="ooo" id="collection1">
    <!-- java bean 就算和資料庫名稱一樣也要寫,不寫就是 null -->
    <id column="deptno" property="deptNo" />
    <result column="dname" property="dName" />
    <!-- <result column="loc" property="loc" /> -->
    
    <collection property="emps" javaType="java.util.Set" ofType="mp.bean.Emp">
        <!-- java bean 就算和資料庫名稱一樣也要寫,不寫就是 null -->
        <id column="empno" property="empno" />
        <result column="ename" property="ename" />
    </collection>
</resultMap>
    
<select id="getDeptAndEmpsById" resultMap="collection1">
    select empno, ename, job, mgr, hiredate, sal, comm, e.deptno, 
        d.dname, d.loc
    from emp e, dept d 
    where e.deptno = d.deptno
    and d.deptno = #{id}
</select>
    
<resultMap type="ooo" id="collection2">
    <!-- java bean 和 資料庫名稱一樣可不寫,但關聯字段一定要 -->
    <id column="deptno" property="deptNo" />
    <result column="dname" property="dName" />
    <!-- <result column="loc" property="loc" /> -->
    
    <collection property="emps" select="mp.bean.dao.EmpMapper.getEmpByDeptno" 
            javaType="java.util.Set" column="deptno" fetchType="lazy">
        <!-- java bean 和 資料庫名稱一樣可不寫 -->
        <id column="empno" property="empno" />
        <result column="ename" property="ename" />
    </collection>
</resultMap>
    
<select id="getDept2Step" resultMap="collection2">
    select * from dept where deptno = #{id}
</select>

※一對一用的是 association;一對多用的是 collection,一對多只有兩種關聯方式,沒有用「.」的方式

※第一種是 SQL 的功,連 resultMap 裡的東西全部都要打才會有值

※第二種是分步的方式,和資料庫名稱一樣可不打,一樣和懶加載有關

※javaType 可以不寫,不管用 List 或 Set 去接都不會有問題,但要寫就一定要寫正確

2015年7月24日 星期五

JOIN、UNION (DML 四)

※創建並加一些測試資料

CREATE TABLE DEPT(
    DEPTNO  NUMBER(9),
    DNAME  VARCHAR(20),
    CONSTRAINT DEPT_PK PRIMARY KEY(DEPTNO)
);
COMMENT ON TABLE  DEPT IS '部門表';
COMMENT ON COLUMN DEPT.DEPTNO IS '部門代號';
COMMENT ON COLUMN DEPT.DNAME IS '部門名稱';

CREATE TABLE EMP(
    EMPNO  NUMBER(9),
    ENAME  VARCHAR(10),
    DNAME  VARCHAR(20),
    DEPTNO  NUMBER(9),
    CONSTRAINT EMP_PK PRIMARY KEY(EMPNO)
);
COMMENT ON TABLE  EMP IS '員工表';
COMMENT ON COLUMN EMP.EMPNO IS '員工編號';
COMMENT ON COLUMN EMP.ENAME IS '員工姓名';
COMMENT ON COLUMN EMP.DNAME IS '部門名稱';
COMMENT ON COLUMN EMP.DEPTNO IS '部門代號';

INSERT INTO DEPT VALUES(10, '財務部');
INSERT INTO DEPT VALUES(20, '研發部');
INSERT INTO DEPT VALUES(30, '業務部');
INSERT INTO DEPT VALUES(40, '生管部');

INSERT INTO EMP (EMPNO,DNAME,ENAME,DEPTNO) VALUES (31,'業務部','KING',10);
INSERT INTO EMP (EMPNO,DNAME,ENAME,DEPTNO) VALUES (32,'業務部','BLAKE',30);
INSERT INTO EMP (EMPNO,DNAME,ENAME,DEPTNO) VALUES (33,'業務部','CLARK',10);
INSERT INTO EMP (EMPNO,DNAME,ENAME,DEPTNO) VALUES (34,'業務部','JONES',20);
INSERT INTO EMP (EMPNO,DNAME,ENAME,DEPTNO) VALUES (35,'業務部','MARTIN',30);
INSERT INTO EMP (EMPNO,DNAME,ENAME,DEPTNO) VALUES (36,'業務部','ALLEN',30);
INSERT INTO EMP (EMPNO,DNAME,ENAME,DEPTNO) VALUES (37,'業務部','TURNER',30);
INSERT INTO EMP (EMPNO,DNAME,ENAME,DEPTNO) VALUES (38,'業務部','SMITH',30);
INSERT INTO EMP (EMPNO,DNAME,ENAME,DEPTNO) VALUES (39,'業務部','WARD',30);
INSERT INTO EMP (EMPNO,DNAME,ENAME,DEPTNO) VALUES (40,'業務部','MILLER',50);

結果:EMP有 10筆; DEPT有 4筆


※SQL:1999語法

也就是SQL有個標準,這個標準是各家資料庫都要遵守的,所以以下介紹的語法是萬能的
SELECT * FROM EMP CROSS JOIN DEPT;
SELECT * FROM EMP NATURAL JOIN DEPT;
SELECT * FROM EMP INNER JOIN DEPT USING(DEPTNO);
SELECT * FROM EMP E INNER JOIN DEPT D ON(E.EMPNO = D.DEPTNO);

※內聯接(INNER JOIN):

.INNER 可以省略不打
.簡單來說,內聯接就是不顯示NULL的
.CROSS JOIN:因為EMP有10筆,DEPT有4筆,所以總共是10*4=40筆
以兩張圖的左邊序號來說,就是1-1.1-2. 1-3. 1-4 .2-1 .2-2 .2-3. 2-4以此類推,推到10-4,所以共40筆,這是最沒用的東西,因為有很多資料都沒有用,所以要想辦法把不要的去掉,所以才會有一些USING啦!ON啦!的語法
這個又叫Cartesian(笛卡爾積),非常有名,join主要就是想辦法消除它
.NATURAL JOIN:會以欄位名稱一樣的,自動幫我們JOIN
這兩張表有DNAME和DEPTNO一樣,所以會幫我們將欄位名稱一樣的JOIN起來
.JOIN…USING:第二行是全部幫我們JOIN起來,但如果只想將一個欄位JOIN起來就好,就可以用USING這個東東
.JOIN…ON:如果欄位名稱不一樣想JOIN或者像USING只能有一個欄位,想要多加一些欄位,就可以用ON
.第四行是無敵的,如下:
SELECT * FROM EMP NATURAL JOIN DEPT;
SELECT * FROM EMP E INNER JOIN DEPT D ON(E.DEPTNO = D.DEPTNO AND E.DNAME = D.DNAME);
SELECT * FROM EMP INNER JOIN DEPT USING(DEPTNO);
SELECT * FROM EMP E INNER JOIN DEPT D ON(E.DEPTNO = D.DEPTNO);
第一行和第二行是一樣的結果; 第三行和第四行也是一樣的結果,但是用JOIN…ON比較麻煩,所以才會發明JOIN…USING和NATURAL JOIN

.NATURAL JOIN 不會顯示重覆的欄位
.JOIN…USING和JOIN…ON還有兩點不同,除了顯示的順序外,就是USING會把打進去的欄位(DEPTNO)整行不顯示,如下右圖少一個欄位:
SELECT * FROM EMPLOYEE E INNER JOIN DEPARTMENT D ON(E.DEPTNO = D.DEPTNO);
SELECT * FROM EMPLOYEE E INNER JOIN DEPARTMENT D USING(DEPTNO);


※外聯接(OUTER JOIN):

.OUTER 可以省略不打
.簡單來說,外聯接就是顯示NULL的

※左聯接

SELECT * FROM EMP E INNER JOIN DEPT D ON(D.DEPTNO = E.DEPTNO);
SELECT * FROM EMP E LEFT OUTER JOIN DEPT D ON(D.DEPTNO = E.DEPTNO);
也可
SELECT * FROM EMP E LEFT OUTER JOIN DEPT D USING(DEPTNO);
有外聯接的會多第10行,成為有人沒部門的情況,如果只想顯示這行可以加條件,如下:
SELECT * FROM EMP E LEFT OUTER JOIN DEPT D ON(E.DEPTNO = D.DEPTNO) WHERE D.DEPTNO IS NULL;
這樣下就只會有第10筆了,可參考這個網站
所以LEFT OUTER JOIN就是左表的欄位(ON裡的欄位)對應到NULL的把它顯示出來,以上面的網站的圖來講,中間就是相同的部分,也就是1~9,左邊就是第10筆了

※右聯接

右聯接就是相反而已,成為有部門沒人的情況
SELECT * FROM EMP E RIGHT OUTER JOIN DEPT D USING(DEPTNO);
SELECT * FROM EMP E RIGHT OUTER JOIN DEPT D USING(DEPTNO) WHERE E.DEPTNO IS NULL;

※全聯接

也就是有部門沒人和有人沒部門的綜合體,所以會有11筆(9筆共同的「中間的」)和2筆(左和右聯接各一筆)NULL的
SELECT * FROM EMP E FULL OUTER JOIN DEPT D USING(DEPTNO);
SELECT * FROM EMP E FULL OUTER JOIN DEPT D USING(DEPTNO) WHERE D.DEPTNO IS NULL OR E.DEPTNO IS NULL;



※Oracle的JOIN語法

※內聯接

SELECT * FROM EMP, DEPT;
SELECT * FROM EMP E, DEPT D WHERE E.DEPTNO = D.DEPTNO;
.表和表之間用逗點隔開就可以了,等同CROSS JOIN,消除迪卡爾積就用一般的WHERE就可以了
.用逗點隔開就不能再用USING和ON了

※外聯接

SELECT * FROM EMP E LEFT OUTER JOIN DEPT D USING(DEPTNO);
SELECT * FROM EMP E, DEPT D WHERE D.DEPTNO(+) = E.DEPTNO;
.這兩行是一樣的,都是左聯接
.第一行是標準語法,所以EMP有對應到NULL的要顯示出來
.Oracle的(+)表示要顯示NULL的那一方
.不要想去判斷左或右聯接,要以邏輯的角度去想,譬如有人沒部門,那麼沒部門(D)那裡一定有(+),所以不要管左右了

※全聯接

Oracle沒有提供全聯接,不可以兩邊都有(+),會報「ORA-01468: 一個述詞只可以參用一個外部結合的表格」的錯,所以只能用標準語法



※多表連接

以inner join 為例

-- 標準的多表連接
SELECT * FROM 
T1 t1 [INNER] JOIN T2 t2 ON(t1.xxx = t2.ooo)
        [INNER] JOIN T3 t3 ON(t1.xxx = t3.zzz)
      -- ...
-- Oracle 的多表連接
SELECT * FROM 
T1 t1, T2 t2, T3 t3 --, T4 [t4], T5 [t5], ...
WHERE t1.xxx = t2.ooo
AND t1.xxx = t3.zzz;


※Union

聯集,Union容易和Join混淆
其中JOB和DNAME的類型順序必須相同,否則會出「ORA-01790: 表示式的資料類型必須與相對應的表示式相同 01790. 00000 -  "expression must have same datatype as corresponding expression"」的錯

SELECT DNAME FROM DEPT WHERE DEPTNO = 30
結果:(有1筆)
SALES
SELECT JOB FROM EMP WHERE SAL > 2000
結果:(有7筆)
CLERK
MANAGER
MANAGER
MANAGER
ANALYST
PRESIDENT
ANALYST
SELECT DNAME FROM DEPT WHERE DEPTNO = 30
UNION ALL
SELECT JOB FROM EMP WHERE SAL > 2000
結果:(UNION ALL 不會去掉重覆的值)(7+1=8筆)
SALES
CLERK
MANAGER
MANAGER
MANAGER
ANALYST
PRESIDENT
ANALYST

SELECT DNAME FROM DEPT WHERE DEPTNO = 30
UNION
SELECT JOB FROM EMP WHERE SAL > 2000
結果:(UNION 去掉重覆的值)
ANALYST
CLERK
MANAGER
PRESIDENT
SALES
所以使用UNION語法就是把兩張表全部SHOW出來,包括NULL

※使用UNION或UNION ALL的效能比OR還要好



※MINUS

既然講了UNION,那就順便講MINUS和INTERSECT了,因為官網是一起講的
minus就是減的意思,第一筆資料減第二筆資料相同的值,最後在distinct並依小到大排序(ASC),假設有兩筆資料
A:5、7、8、8
B:7、9、2
A minus B==>5、8
B minus A==>2、9

※範例1

SELECT deptno FROM emp
WHERE empno IN(7902,7499,7876,7900)-- 30 20 30 20
MINUS
SELECT deptno FROM emp
WHERE empno IN(7782,7654,7698,7934);-- 30 30 10 10

※依照上面的說法,答按是20



※範例2

SELECT deptno FROM emp
WHERE empno IN(7782,7654,7698,7934)-- 30 30 10 10
MINUS
SELECT deptno FROM emp
WHERE empno IN(7902,7876);-- 20 20

※什麼都減不到,但仍要distinct和排序,答案是10 30


※範例3

SELECT * FROM EMP --14筆
MINUS
SELECT * FROM EMP WHERE DEPTNO = 10 --3筆

※答案是14-3,所以是11筆資料量



※INTERSECT

交集,只抓重覆的資料
SELECT * FROM EMP
INTERSECT
SELECT * FROM EMP WHERE DEPTNO = 10;

※這要看資料,我的情況是重覆的資料有3筆

2015年7月23日 星期四

雙向一對一關聯 Mybatis3.x(六)

※事前規劃

假裝有一個USER_NAME表,因為之前沒規劃好,想多加幾個欄位,怕在這張表增加以後會影響現有的行為,所以增加一張表,叫USER_DATA,這樣就成了一個一對一的關聯
CREATE TABLE USER_NAME(
    USERID            VARCHAR(10) ,
    NAME            VARCHAR(20) ,
    CONSTRAINT USER_PK PRIMARY KEY(USERID)
);

CREATE TABLE USER_DATA(
    USERID            VARCHAR(10) ,
    AGE                NUMBER(3) ,
    BIRTHDAY        DATE ,
    CONSTRAINT USER_DATA_PK PRIMARY KEY(USERID) ,
    CONSTRAINT USER_DATA_FK FOREIGN KEY(USERID) REFERENCES USER_NAME(USERID) ON DELETE CASCADE
);

COMMENT ON TABLE USER_DATA IS '使用者資料表';
COMMENT ON COLUMN USER_DATA.USERID IS '使用者id';
COMMENT ON COLUMN USER_DATA.AGE IS '使用者年齡';
COMMENT ON COLUMN USER_DATA.BIRTHDAY IS '使用者生日';

COMMENT ON TABLE USER_NAME IS '使用者名稱表';
COMMENT ON COLUMN USER_NAME.USERID IS '使用者id';
COMMENT ON COLUMN USER_NAME.NAME IS '使用者姓名';

INSERT INTO USER_NAME(USERID, NAME)VALUES(1, 'xxx');
INSERT INTO USER_DATA(USERID, AGE, BIRTHDAY)VALUES(1, 20, TO_DATE('19990125', 'YYYYMMDD'));


因為是雙向,所以兩張java都各增加一個屬性連到對方:
public class UserName {
    private String userId;
    private String name;
    private UserData userData;
    //setter/getter...
}

public class UserData {
    private String userId;
    private Integer age;
    private Date birthday;
    private UserName userName;
    //setter/getter...
}

※association設定

因為是雙向,兩張xml都要設定,UserName.xml:
<mapper namespace="ooo.UserName">
    <resultMap type="UserName" id="mapperUserName">
        <id property="userId" column="USERID" />
        <result property="name" column="NAME" />
        
        <association property="userData" column="USERID" javaType="UserData"  select="xxx.UserData.getUserDataById"/>
    </resultMap>

    <select id="getUserNameById" parameterType="int" resultMap="mapperUserName">
        SELECT * FROM USER_NAME WHERE USERID = #{userId}
    </select>
</mapper>

UserData.xml:
<mapper namespace="xxx.UserData">
    <resultMap type="UserData" id="mapperUserData">
        <id property="userId" column="USERID" />
        <result property="age" column="AGE" />
        <result property="birthday" column="BIRTHDAY" />
        
        <association property="userName" column="USERID" javaType="UserName" select="ooo.UserName.getUserNameById" />
    </resultMap>

    <select id="getUserDataById" parameterType="int" resultMap="mapperUserData">
        SELECT * FROM USER_DATA WHERE USERID = #{userId}
    </select>
</mapper>

javaType因為有別名的關係,所以才這樣寫,select要namespace.id,選對方的
測試類:
SqlSession sqlSession = MyBatisSessionFactory.getSession();
        
UserName un = sqlSession.selectOne("ooo.UserName.getUserNameById", 1);
System.out.println("getUserId=" + un.getUserId());
System.out.println("getName=" + un.getName());
if(un.getUserData() != null){
    System.out.println("======================================");
    System.out.println("getAge=" + un.getUserData().getAge());
    System.out.println("getBirthday=" + un.getUserData().getBirthday());
}

System.out.println("----------------------------------------");
UserData ud = sqlSession.selectOne("xxx.UserData.getUserDataById", 1);
System.out.println("getUserId=" + ud.getUserId());
System.out.println("getAge=" + ud.getAge());
System.out.println("getBirthday=" + ud.getBirthday());
if(ud.getBirthday() != null){
    System.out.println("======================================");
    System.out.println("getName=" + ud.getUserName().getName());
}

MyBatisSessionFactory.closeSession();

如果不設association,連到對方的屬性會是null



※一對一關聯的三種方式

上面的方式是使用第三種方式,會有兩條 SQL 語句,其他兩種都一條


<resultMap type="mp.bean.Emp" id="association1">
    <!-- java bean 和 資料庫名稱一樣可不寫 -->
    <result column="dname" property="dept.dName"/>
    <result column="loc" property="dept.loc"/>
</resultMap>
    
<resultMap type="mp.bean.Emp" id="association2">
    <!-- java bean 就算和資料庫名稱一樣也要寫,不寫就是 null -->
    <id column="empno" property="empno" />
    <result column="ename" property="ename" />
    
    <association property="dept" javaType="mp.bean.Dept">
        <!-- java bean 就算和資料庫名稱一樣也要寫,不寫就是 null -->
        <id column="deptNo" property="deptNo" />
        <result column="dName" property="dName" />
    </association>
</resultMap>
    
<select id="getEmpById" resultMap="association1">
    select empno, ename, job, mgr, hiredate, sal, comm, e.deptno, 
        d.dname, d.loc
    from emp e, dept d 
    where e.deptno = d.deptno
    and empno = #{id}
</select>
    
    
    
<resultMap type="mp.bean.Emp" id="association3">
    <!-- java bean 和 資料庫名稱一樣可不寫 -->
    <association property="dept" column="deptno" 
        select="mp.bean.dao.DeptMapper.getDeptById" />
</resultMap>
    
<select id="getEmp2Step" resultMap="association3">
    select * from emp where empno = #{id}
</select>
    
    
-------------------------------------------------------
<mapper namespace="mp.bean.dao.DeptMapper">
    <select id="getDeptById" resultType="mp.bean.Dept">
        select * from dept where
        deptno = #{deptNo}
    </select>
</mapper>


※第一種是用「.」的方式,dept 是 java bean 的欄位名稱

※第二種都是 SQL 的功,缺點是什麼都要寫,否則就是 null

※第三種裡的 select 是連到其他 mapper 的 SQL,property 是 java bean 的欄位名稱
column 是從 getEmp2Step 取到的欄位名稱,有很多,當然是給要關聯的欄位名稱

第三種是為了提高效能用的,在全域的 setting 有兩個可以設定:
1.lazyLoadingEnabled:true為懶加載,但只要sql的xml裡的屬性fecthType有設定,不管是lazy或eager,都會被fecthType給覆蓋,可以在全域設定懶加載,針對特定的sql不使用懶加載,預設為 false
2.aggressiveLazyLoading:和 lazyLoadingEnabled 相反,false為懶加載;只要一給true,lazyLoadingEnabled、fetchType 都沒用了,預設為 false,但小於等於 3.4.1 版是 true,所以 3.4.1 之前如果不設為 false,lazyLoadingEnabled 會沒有用

2015年7月21日 星期二

resultMap的鍳別器 Mybatis3.x(五)

※resultMap的discriminator

為了練習用,再增加幾個欄位
ALTER TABLE CHESS ADD SCORE NUMBER(3);
ALTER TABLE CHESS ADD SCHOOL VARCHAR(20);
ALTER TABLE CHESS ADD SALARY NUMBER(7);
ALTER TABLE CHESS ADD COMPANY VARCHAR(20);
ALTER TABLE CHESS ADD CLASS VARCHAR(3);

COMMENT ON COLUMN CHESS.SCORE IS '學生學棋的分數';
COMMENT ON COLUMN CHESS.SCHOOL IS '學生的學校名稱';
COMMENT ON COLUMN CHESS.SALARY IS '員工製造棋的薪水';
COMMENT ON COLUMN CHESS.COMPANY IS '員工公司名稱';
COMMENT ON COLUMN CHESS.CLASS IS '分類:1學生 2員工';

或者乾脆重新建一張
CREATE TABLE CHESS (    
    CHESS_NO    NUMBER(10), 
    NAME    VARCHAR2(10), 
    PRICE    NUMBER(5), 
    PRODUCT_DATE    DATE, 
    SCORE    NUMBER(3), 
    SCHOOL    VARCHAR2(20), 
    SALARY    NUMBER(7), 
    COMPANY    VARCHAR2(20), 
    CLAZZ    VARCHAR2(3), 
    CONSTRAINT CHESS_PK PRIMARY KEY (CHESS_NO)
);
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 '棋的生產日期';
COMMENT ON COLUMN CHESS SCORE IS '學生學棋的分數';
COMMENT ON COLUMN CHESS SCHOOL IS '學生的學校名稱';
COMMENT ON COLUMN CHESS SALARY IS '員工製造棋的薪水';
COMMENT ON COLUMN CHESS COMPANY IS '員工公司名稱';
COMMENT ON COLUMN CHESS CLAZZ IS '分類:1學生 2員工';
COMMENT ON TABLE  CHESS IS '棋表';

現在就是說新增的欄位CLAZZ可以區分成學生和員工兩部分,在資料庫是一張表,但在java把它區分開來,變三張表
因為CLAZZ是區分用的,所以要在原本的vo增加class,其他兩張分別是Student.java和Employee.java,把對應的兩個屬性放在裡面,然後給setter/getter,繼承一下原本的Chess.java
發現class在java是關鍵字,改一下
ALTER TABLE CHESS RENAME COLUMN CLASS TO CLAZZ;

三張java表如下:
Chess.java
public class Chess {
    private Integer chessNo;
    private String name;
    private Integer price;
    private Date productDate;
    private String clazz;
}
//setter/getter

Student.java
public class Strudent extends Chess {
    private int score;
    private String school;
}
//setter/getter

Employee.java
public class Employee extends Chess {
    private int salary;
    private String company;
}
//setter/getter

重點來了,Chess.xml
<sql id="column">
    CHESS_NO, NAME, PRICE, PRODUCT_DATE, CLAZZ
</sql>

<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" />
    
    <discriminator javaType="java.lang.String" column="CLAZZ">
        <case value="1" resultType="Student">
            <result property="score" column="SCORE"/>
            <result property="school" column="SCHOOL"/>
        </case>
        <case value="2" resultType="Employee">
            <result property="salary" column="SALARY"/>
            <result property="company" column="COMPANY"/>
        </case>
    </discriminator>
</resultMap>

<insert id="insertStudent" parameterType="Chess">
    <selectKey keyProperty="chessNo" order="BEFORE" resultType="java.lang.Integer">
        SELECT CHESS_SEQUENCE.NEXTVAL FROM DUAL
    </selectKey>
    INSERT INTO CHESS(
        <include refid="column" />, SCORE, SCHOOL
    )
    VALUES(#{chessNo}, #{name}, #{price}, #{productDate}, #{clazz}, #{score}, #{school})
</insert>

<insert id="insertEmployee" parameterType="Chess">
    <selectKey keyProperty="chessNo" order="BEFORE" resultType="java.lang.Integer">
        SELECT CHESS_SEQUENCE.NEXTVAL FROM DUAL
    </selectKey>
    INSERT INTO CHESS(
        <include refid="column" />, SALARY, COMPANY
    )
    VALUES(#{chessNo}, #{name}, #{price}, #{productDate}, #{clazz}, #{salary}, #{company})
</insert>
測試類沒什麼特別(clazz可以用enum去設計較漂亮):
Student stu = new Student();
stu.setName("孔明棋");
stu.setPrice(20);
stu.setProductDate(new Date());
stu.setClazz("1");
stu.setScore(70);
stu.setSchool("諸葛亮小學");
System.out.println(sqlSession.insert("Chess.insertStudent", stu));

Employee emp = new Employee();
emp.setName("孔明棋");
emp.setPrice(20);
emp.setProductDate(new Date());
emp.setClazz("2");
emp.setSalary(50000);
emp.setCompany("小棋玩具社");
System.out.println(sqlSession.insert("Chess.insertEmployee", emp));

2015年7月19日 星期日

動態SQL Mybatis3.x(四)

※動態SQL

有個要注意的地方
要判斷大小用"<"和">",但"<"在XML有特殊的意義,它會以為是標籤,如<h1><head><configuration>…等,有兩種方式可解決

一.CDATA

<![CDATA[
程式碼…
]]>
這種方式是包起來的部分不會對它做認何的轉譯之類的

二.實體參照

如&nbsp;就是空格,&quot;就是",這裡有較詳細的表可參考

----------------------------------------------------------------------------------------------------------
以下的標籤都是用這個且標籤一定要是小寫
<select id="testDynamicSQL" parameterType="Chess" resultMap="ChessInterface" >
</select>
測試類如下:
System.out.println("----------Chess.testDynamicSQL----------");
Chess chess = new Chess();
//    chess.setChessNo(12);
//    chess.setPrice(50);

List<Chess> listChess = sqlSession.selectList("Chess.testDynamicSQL", chess);
for(Chess c:listChess){
    System.out.println("chessNo=" + c.getChessNo());
    System.out.println("name=" + c.getName());
    System.out.println("price=" + c.getPrice());
    System.out.println("productDate=" + c.getProductDate());
    System.out.println("---------------------------------");
}

※<if>和<choose><when><otherwise>

以java的角度看if就是if沒有else;
choose裡面包when和otherwise,when就是if,otherwise就是else
SELECT
    <include refid="column" />
FROM CHESS
WHERE 1=1
<if test="chessNo != null">
    AND CHESS_NO = #{chessNo}
</if>
<if test="name != null">
    AND NAME = #{name}
</if>
<if test="price &lt; 30">
    AND PRICE = #{price}
</if>
<if test="productDate != null">
    AND PRODUCT_DATE = #{productDate}
</if>
    ORDER BY PRICE



SELECT
    <include refid="column" />
FROM CHESS
WHERE 1=1

<choose>
    <when test="chessNo != null">
        AND CHESS_NO = #{chessNo}
    </when>
    <when test="name != null">
        AND NAME = #{name}
    </when>
    <otherwise>
        AND PRICE = #{price}
    </otherwise>
</choose>
ORDER BY PRICE

※<where>和<trim>和<set>

上個例子的WHERE 1=1看起來看怪,所以mybatis提供一個自動幫我們判斷需不需要WHERE,也能智慧的去掉AND、OR的功能,以上面的例子就可以這樣寫:
SELECT
    <include refid="column" />
FROM CHESS
<WHERE>
    <if test="chessNo != null">
        AND CHESS_NO = #{chessNo}
    </if>
    <if test="name != null">
        AND NAME = #{name}
    </if>
    <if test="price &lt; 30">
        AND PRICE = #{price}
    </if>
    <if test="productDate != null">
        AND PRODUCT_DATE = #{productDate}
    </if>
</WHERE>
ORDER BY PRICE


SELECT
    <include refid="column" />
FROM CHESS
<WHERE>
    <choose>
        <when test="chessNo != null">
            AND CHESS_NO = #{chessNo}
        </when>
        <when test="name != null">
            AND NAME = #{name}
        </when>
        <otherwise>
            AND PRICE = #{price}
        </otherwise>
    </choose>
</WHERE>
ORDER BY PRICE

如果有特殊須求可以用<trim>
SELECT
    <include refid="column" />
FROM CHESS
<trim prefix="WHERE" prefixOverrides="ORDER BY PRICE|AND|OR">
    <choose>
        <when test="chessNo != null">
            AND CHESS_NO = #{chessNo}
        </when>
        <when test="name != null">
            AND NAME = #{name}
        </when>
        <otherwise>
            ORDER BY PRICE 1=1
        </otherwise>
    </choose>
</trim>
prefix就是在最前面增加什麼;
prefixOverrides就是將最前面的什麼取代成空,有順序問題,最前面找到了,就先取代
還有suffix和suffixOverrides就是在最後面增加什麼,和將最後面的什麼取代成空

而set是配合update的,如下:
<update id="update" parameterType="Chess">
    UPDATE CHESS
    <set>
        <if test="chessNo != null">
            NAME = #{name},
        </if>
        <if test="price != null">
            PRICE = #{price},
        </if>
    </set>
    <where>
        CHESS_NO = #{chessNo}
    </where>
</update>
會幫我們判斷最後面的逗點,會把他變不見
所以<where>就等於
<trim prefix="WHERE" prefixOverrides="AND |OR "></trim>
而<set>就等於
<trim prefix="SET" suffixOverrides=","></trim>
所以<where>和<set>,其實就是<trim>分出來的,可能因為常用的關係



<bind>

Dept d = new Dept();
d.setdName("O");
List<Dept> bind1 = mapper.getDeptsByDeptUseBind(d);
bind1.stream().forEach(x -> {
    System.out.println(x.getdName());
});
    
Map<String, String> map = new HashMap<>();
map.put("kkk", "O");
List<Dept> bind2 = mapper.getDeptsByMapUseBind(map);
bind2.stream().forEach(x -> {
    System.out.println(x.getdName());
});


※參數給 java bean 或 Map 都可以


<select id="getDeptsByDeptUseBind" resultType="mp.bean.Dept" parameterType="mp.bean.Dept">
    <!-- <bind name="xxx" value="'%' + dName + '%'"/> -->
    <bind name="xxx" value="'%' + _parameter.getdName() + '%'" />
    select * from dept 
    where dname like #{xxx}
</select>
    
<select id="getDeptsByMapUseBind" resultType="mp.bean.Dept" parameterType="mp.bean.Dept">
    <!-- <bind name="xxx" value="'%' + kkk + '%'"/> -->
    <bind name="xxx" value="'%' + _parameter.kkk + '%'" />
    select * from dept 
    where dname like #{xxx}
</select>


※ _parameter 是內鍵的

※這兩種方式都有兩種用法,其中 dName 是 java bean 的名稱

※用 Map 時,_parameter 必須要.key 的名稱


※<foreach>

主要是給IN使用的,如下:
SELECT
    <include refid="column" />
FROM CHESS
<where>
    PRICE IN
    <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
        #{item}
    </foreach>
</where>
.item為foreach裡要使用的變數名稱
.index索引,我沒加也ok,可能預設會幫我們加,會自動從0開始
.collection有 list、array、Map 的 key 名稱、@Param 裡的名稱,Set 一樣給 list
※1.但 list、array 如果使用 @Param,變數就只能是 @Param的名稱或 param1,統一用@Param ,XML 就不用改了
    2.Map 如果使用 @Param,變數就只能是 @Param的名稱.key名稱或 param1.key名稱
.open開始時用什麼符號開始,IN當然是「(」
.close結束時用什麼符號結束,IN當然是「)」
.separator分開始用什麼符號,IN當然是「,」,如果寫在foreach裡,最後會多一個,,所以這個功能會幫我們判斷並刪除
測試類:
List<Integer> l = new ArrayList<>();
l.add(30);
l.add(40);
l.add(50);
List<Chess> listChess = sqlSession.selectList("Chess.testDynamicSQL", l);

for(Chess c:listChess){
    System.out.println("chessNo=" + c.getChessNo());
    System.out.println("name=" + c.getName());
    System.out.println("price=" + c.getPrice());
    System.out.println("productDate=" + c.getProductDate());
    System.out.println("---------------------------------");
}

如果collection想給array,就改第二個參數就可以了
List<Chess> listChess = sqlSession.selectList("Chess.testDynamicSQL", new Integer[]{30,40,50});
如果collection和你傳的參數不匹配, 就會出Parameter 'list' not found.的錯,''裡面是你在collection裡打的字

還可以放map:
List<Integer> l = new ArrayList<>();
l.add(30);
l.add(40);
l.add(50);

Map<String, List<Integer>> map = new HashMap<>();
map.put("xxx", l);

List<Chess> listChess = sqlSession.selectList("Chess.testDynamicSQL", map);
for(Chess c:listChess){
    System.out.println("chessNo=" + c.getChessNo());
    System.out.println("name=" + c.getName());
    System.out.println("price=" + c.getPrice());
    System.out.println("productDate=" + c.getProductDate());
    System.out.println("---------------------------------");
}
記得collection也要給map的變數xxx,不然會出The expression 'list' evaluated to a null value.的錯

※證明index從0開始

我的資料庫有如下資料:

List重點是從0~9跑了10次迴圈,裡面放什麼不重要
List<Integer> l = new ArrayList<>();
for(int i=0; i<10; i++){
    l.add(30);
}
List<Chess> listChess = sqlSession.selectList("Chess.testDynamicSQL", l);
for(Chess c:listChess){
    System.out.println("chessNo=" + c.getChessNo());
    System.out.println("name=" + c.getName());
    System.out.println("price=" + c.getPrice());
    System.out.println("productDate=" + c.getProductDate());
    System.out.println("---------------------------------");
}

因為要用到index, 我給ooo,不打不行,我想說預設值應該是index,結果也不行,反正要用到就打就對了
SELECT
    <include refid="column" />
FROM CHESS
<where>
    CHESS_NO IN
    <foreach item="item" index="ooo" collection="list" open="(" separator="," close=")">
        #{ooo}
    </foreach>
</where>

結果:
----------Chess.testDynamicSQL----------
chessNo=7
name=象棋
price=50
productDate=Sat Jul 18 16:01:37 CST 2015
---------------------------------
chessNo=8
name=西洋棋
price=120
productDate=Sat Jul 18 16:02:20 CST 2015
---------------------------------
chessNo=9
name=跳棋
price=30
productDate=Sat Jul 18 16:03:10 CST 2015
---------------------------------

chessNo我的資料庫有10的沒顯示出來,我的迴圈跑10次,那就表示它一定是從0開始跑,跑到9,所以9有跑出來
index本身是數字,所以裡面還可以運算,我有在裡面加1試試,還真的10就跑出來了



※大量增刪改

Oracle:


Emp e1 = new Emp();
e1.setEname("ccc");
e1.setDeptno(20);
Emp e2 = new Emp();
e2.setEname("ddd");
e2.setDeptno(30);
List<Emp> list = new ArrayList<>();
list.add(e1);
list.add(e2);
    
mapper.bigInsert1(list);
//mapper.bigInsert2(list);
//mapper.bigUpdate(Arrays.asList("xxx", "ooo"));
//mapper.bigDelete(Arrays.asList("xxx", "ooo"));
mapper.getEmp().stream().forEach(x -> {
    System.out.println(x.getEname());
});



public void bigInsert1(@Param("emp") List<Emp> list);
public void bigInsert2(@Param("emp") List<Emp> list);
public void bigUpdate(List<String> list);
public void bigDelete(List<String> list);



<insert id="bigInsert1">
    <foreach collection="emp" open="begin" close="end;" item="e">
        insert into emp(empno, ename, job, mgr, hiredate, sal, comm, deptno)
        values(emp_seq.nextval, #{e.ename}, #{e.job}, #{e.mgr}, #{e.hiredate}, #{e.sal}, #{e.comm}, #{e.deptno});
    </foreach>
</insert>
    
<insert id="bigInsert2">
    insert into emp(empno, ename, job, mgr, hiredate, sal, comm, deptno)
    <foreach collection="emp" close=")" item="e" separator="union all"
        open="select emp_seq.nextval, ename, job, mgr, hiredate, sal, comm, deptno from(">
    
        select #{e.ename} ename, #{e.job} job, #{e.mgr} mgr, #{e.hiredate} hiredate, 
               #{e.sal} sal, #{e.comm} comm, #{e.deptno} deptno
        from dual
    </foreach>
</insert>
    
<update id="bigUpdate">
    <foreach collection="list" item="v" open="begin" close="end;">
        update emp
        <set>
            ename = #{v}
        </set>
        <where>
            ename in('aaa', 'bbb');
        </where>
    </foreach>
</update>
    
<delete id="bigDelete">
    delete from emp
    <where>
        ename in
        <foreach collection="list" item="v" separator="," open="(" close=")">
            #{v}
        </foreach>
    </where>
</delete>

※簡單的新增和查詢都必需在最前加上 begin,和最後加上 end; 否則報錯

※第二種新增看起來較複雜,以下是 SQL
insert into table_name(id, f1, f2)
    select emp_seq.nextval, f1, f2 from (
        select 'xxx1' f1, 'ttt1' f2 from dual
        union all
        select 'xxx2' f1, 'ttt2' f2 from dual
        union all
        select 'xxx3' f1, 'ttt3' f2 from dual
    )



MySQL:

mysql 可以不用 begin end; 即可新增,但必須在 jdbc.url 後加東西,jdbc.url=jdbc:mysql://localhost:3306/test?allowMultiQueries=true

另外還有一種是 insert into () values(),(),(),Oracle 沒有
<insert id="bigInsert1">
    <foreach collection="emps" item="emp" separator=";">
        insert into emp(t1,t2,t3) values
        (#{emp.f1},#{emp.f2},#{emp.f3},#{emp.f4})
    </foreach>
</insert>
    
<insert id="bigInsert2">
    insert into emp(t1,t2,t3) values
    <foreach collection="emps" item="emp" separator=",">
        (#{emp.f1},#{emp.f2},#{emp.f3})
    </foreach>
</insert>

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 使用