本文篇幅較長,建議合理利用右上角目錄進行查看(若是沒有目錄請刷新)。html
本文主要總結於劉增輝的《MyBatisc從入門到精通》一書,有興趣的朋友能夠自行研讀前端
建議仔細研讀官方文檔:java
http://www.mybatis.org/mybatis-3/zh/mysql
http://www.mybatis.org/spring/zh/git
http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/github
DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用戶ID', `user_name` varchar(50) DEFAULT NULL COMMENT '用戶名', `user_password` varchar(50) DEFAULT NULL COMMENT '密碼', `user_email` varchar(50) DEFAULT 'test@mybatis.tk' COMMENT '郵箱', `user_info` text COMMENT '簡介', `head_img` blob COMMENT '頭像', `create_time` datetime DEFAULT NULL COMMENT '建立時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1035 DEFAULT CHARSET=utf8 COMMENT='用戶表'; DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID', `role_name` varchar(50) DEFAULT NULL COMMENT '角色名', `enabled` int(11) DEFAULT NULL COMMENT '有效標誌', `create_by` bigint(20) DEFAULT NULL COMMENT '建立人', `create_time` datetime DEFAULT NULL COMMENT '建立時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='角色表'; DROP TABLE IF EXISTS `sys_privilege`; CREATE TABLE `sys_privilege` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '權限ID', `privilege_name` varchar(50) DEFAULT NULL COMMENT '權限名稱', `privilege_url` varchar(200) DEFAULT NULL COMMENT '權限URL', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='權限表'; DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( `user_id` bigint(20) DEFAULT NULL COMMENT '用戶ID', `role_id` bigint(20) DEFAULT NULL COMMENT '角色ID' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶角色關聯表'; DROP TABLE IF EXISTS `sys_role_privilege`; CREATE TABLE `sys_role_privilege` ( `role_id` bigint(20) DEFAULT NULL COMMENT '角色ID', `privilege_id` bigint(20) DEFAULT NULL COMMENT '權限ID' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色權限關聯表';
INSERT INTO `sys_privilege` VALUES ('1', '用戶管理', '/users'); INSERT INTO `sys_privilege` VALUES ('2', '角色管理', '/roles'); INSERT INTO `sys_privilege` VALUES ('3', '系統日誌', '/logs'); INSERT INTO `sys_privilege` VALUES ('4', '人員維護', '/persons'); INSERT INTO `sys_privilege` VALUES ('5', '單位維護', '/companies'); INSERT INTO `sys_role` VALUES ('1', '管理員', '1', '1', '2016-04-01 17:02:14'); INSERT INTO `sys_role` VALUES ('2', '普通用戶', '1', '1', '2016-04-01 17:02:34'); INSERT INTO `sys_role_privilege` VALUES ('1', '1'); INSERT INTO `sys_role_privilege` VALUES ('1', '3'); INSERT INTO `sys_role_privilege` VALUES ('1', '2'); INSERT INTO `sys_role_privilege` VALUES ('2', '4'); INSERT INTO `sys_role_privilege` VALUES ('2', '5'); INSERT INTO `sys_user` VALUES ('1', 'admin', '123456', 'admin@mybatis.tk', '管理員用戶', 0x1231231230, '2016-06-07 01:11:12'); INSERT INTO `sys_user` VALUES ('1001', 'test', '123456', 'test@mybatis.tk', '測試用戶', 0x1231231230, '2016-06-07 00:00:00'); INSERT INTO `sys_user_role` VALUES ('1', '1'); INSERT INTO `sys_user_role` VALUES ('1', '2'); INSERT INTO `sys_user_role` VALUES ('1001', '2');
開發中建立實體類可使用MyBatis Generator工具來根據數據庫表來生成實體類spring
package tk.mybatis.simple.model; import java.io.Serializable; /** * 權限表 */ public class SysPrivilege implements Serializable { private static final long serialVersionUID = 6315662516417216377L; /** * 權限ID */ private Long id; /** * 權限名稱 */ private String privilegeName; /** * 權限URL */ private String privilegeUrl; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getPrivilegeName() { return privilegeName; } public void setPrivilegeName(String privilegeName) { this.privilegeName = privilegeName; } public String getPrivilegeUrl() { return privilegeUrl; } public void setPrivilegeUrl(String privilegeUrl) { this.privilegeUrl = privilegeUrl; } }
package tk.mybatis.simple.model; import java.io.Serializable; import java.util.Date; import java.util.List; import tk.mybatis.simple.type.Enabled; /** * 角色表 */ public class SysRole implements Serializable { private static final long serialVersionUID = 6320941908222932112L; /** * 角色ID */ private Long id; /** * 角色名 */ private String roleName; /** * 有效標誌 */ private Enabled enabled; /** * 建立人 */ private String createBy; /** * 建立時間 */ private Date createTime; /** * 用戶信息 */ private SysUser user; /** * 建立信息 */ private CreateInfo createInfo; public CreateInfo getCreateInfo() { return createInfo; } public void setCreateInfo(CreateInfo createInfo) { this.createInfo = createInfo; } /** * 角色包含的權限列表 */ List<SysPrivilege> privilegeList; public List<SysPrivilege> getPrivilegeList() { return privilegeList; } public void setPrivilegeList(List<SysPrivilege> privilegeList) { this.privilegeList = privilegeList; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public Enabled getEnabled() { return enabled; } public void setEnabled(Enabled enabled) { this.enabled = enabled; } public String getCreateBy() { return createBy; } public void setCreateBy(String createBy) { this.createBy = createBy; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public SysUser getUser() { return user; } public void setUser(SysUser user) { this.user = user; } }
package tk.mybatis.simple.model; /** * 角色權限關聯表 */ public class SysRolePrivilege { /** * 角色ID */ private Long roleId; /** * 權限ID */ private Long privilegeId; public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } public Long getPrivilegeId() { return privilegeId; } public void setPrivilegeId(Long privilegeId) { this.privilegeId = privilegeId; } }
package tk.mybatis.simple.model; import java.io.Serializable; import java.util.Date; import java.util.List; /** * 用戶表 */ public class SysUser implements Serializable { private static final long serialVersionUID = -328602757171077630L; /** * 用戶ID */ private Long id; /** * 用戶名 */ private String userName; /** * 密碼 */ private String userPassword; /** * 郵箱 */ private String userEmail; /** * 簡介 */ private String userInfo; /** * 頭像 */ private byte[] headImg; /** * 建立時間 */ private Date createTime; /** * 用戶角色 */ private SysRole role; /** * 用戶的角色集合 */ private List<SysRole> roleList; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserPassword() { return userPassword; } public void setUserPassword(String userPassword) { this.userPassword = userPassword; } public String getUserEmail() { return userEmail; } public void setUserEmail(String userEmail) { this.userEmail = userEmail; } public String getUserInfo() { return userInfo; } public void setUserInfo(String userInfo) { this.userInfo = userInfo; } public byte[] getHeadImg() { return headImg; } public void setHeadImg(byte[] headImg) { this.headImg = headImg; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public SysRole getRole() { return role; } public void setRole(SysRole role) { this.role = role; } public List<SysRole> getRoleList() { return roleList; } public void setRoleList(List<SysRole> roleList) { this.roleList = roleList; } }
package tk.mybatis.simple.model; /** * 用戶角色關聯表 */ public class SysUserRole { /** * 用戶ID */ private Long userId; /** * 角色ID */ private Long roleId; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } }
在 src/main/resources 的 tk.mybatis.simple.mapper 目錄下建立 5 個表各自對應的 XML 文件,分別爲 UserMapper.xml 、 RoleMapper.xml 、 PrivilegeMapper.xml 、 UserRoleMapper.xml 和RolePrivilegeMapper.xml 。sql
在 src/main/java下面建立包 tk.mybatis.simple.mapper 。接着,在該包下建立 XML 文件對應的接口類,分別爲 UserMapper.java 、 RoleMapper.java 、 PrivilegeMapper.java 、UserRoleMapper.java 和 RolePrivilegeMapper.java數據庫
下面以User表對應的XML文件和接口類設計爲例介紹如何實現express
UserMapper.java
package tk.mybatis.simple.mapper; public interface UserMapper { }
UserMapper.xml
此文件用全限定的類名,把接口和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="tk.mybatis.simple.mapper.UserMapper"> </mapper>
mybatis-config.xml
把UserMapper配置到mybatis裏面去,有2種方法:
一、經過接口配置
<mappers> <package name="tk.mybatis.simple.mapper" /> </mappers>
二、經過xml文件配置
<mappers> <mapper resource="tk/mybatis/simple/mapper/UserMapper.xml" /> </mappers>
建議經過接口配置,由於只須要指定接口所在的包,MyBatis會掃描全部接口對應的Mapper
這種方式的運行流程以下:
使用純粹的JDBC時,須要寫查詢語句,而且對結果集進行手動處理,將結果映射到對象的屬性中
使用 MyBatis 時,只須要在XML中添加一個select元素,寫一個SQL,再作一些簡單的配置,就能夠將查詢的結果直接映射到對象中
下面以用id查找用戶爲例,講解select用法:
public interface UserMapper { /** * 經過 id 查詢用戶 * * @param id * @return */ SysUser selectById(Long id); }
<?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="tk.mybatis.simple.mapper.UserMapper"> <resultMap id="userMap" type="tk.mybatis.simple.model.SysUser"> <id property="id" column="id" /> <result property="userName" column="user_name" /> <result property="userPassword" column="user_password" /> <result property="userEmail" column="user_email" /> <result property="userInfo" column="user_info" /> <result property="headImg" column="head_img" jdbcType="BLOB" /> <result property="createTime" column="create_time" jdbcType="TIMESTAMP" /> </resultMap> <select id="selectById" resultMap="userMap"> select * from sys_user where id = #{id} </select> </mapper>
<select>標籤:
映射查詢語句使用的標籤
<resultMap>標籤:
用於配置Java對象的屬性和查詢結果列的對應關係,經過resultMap中配置的column和property能夠將查詢列的值映射到type對象的屬性上,所以當咱們使用select*查詢全部列的時候,MyBatis也能夠將結果正確地映射到SysUser對象上
<resultMap>下的標籤:
<id>和<result>裏面的屬性:
如何定義返回值
接口方法
/** * 查詢所有用戶 * * @return */ List<SysUser> selectAll();
XML設置
<select id="selectAll" resultType="tk.mybatis.simple.model.SysUser"> select id, user_name userName, user_password userPassword, user_email userEmail, user_info userInfo, head_img headImg, create_time createTime from sys_user </select>
這個例子中,select標籤中直接使用resultType,以及使用字段別名,使sql的字段名與類的字段名對上,便可自動映射
綜上,有2種方式:一、在resultMap中配置property屬性和column屬性的映射;二、SQL中設置別名
property或者別名要與對象的屬性名對應才能匹配,實際匹配時,是把字母都轉換成大寫來匹配的,因此不區分大小寫;通常爲了閱讀,應該統一寫法就OK
一種很常見的狀況,數據庫使用下劃線命名方式,如user_Name;而Java中使用駝峯式命名,如userName
MyBatis提供了一個配置,自動將這2種方式進行匹配,在配置文件中設置便可,代碼以下
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
在src/test/java下的tk.mybatis.simple.mapper包中,添加基礎測試類
package tk.mybatis.simple.mapper; import java.io.IOException; 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; import org.junit.BeforeClass; /** * 基礎測試類 */ public class BaseMapperTest { private static SqlSessionFactory sqlSessionFactory; @BeforeClass public static void init(){ try { Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); reader.close(); } catch (IOException ignore) { ignore.printStackTrace(); } } public SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
修改CountryMapperTest類
由於Country和User的Mapper中都有SelectAll,因此不在惟一,須要用全限定類名tk.mybatis.simple.mapper.CountryMapper.selectAll去調用
package tk.mybatis.simple.mapper; import java.util.List; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import tk.mybatis.simple.model.Country; public class CountryMapperTest extends BaseMapperTest { @Test public void testSelectAll() { SqlSession sqlSession = getSqlSession(); try { List<Country> countryList = sqlSession.selectList("tk.mybatis.simple.mapper.CountryMapper.selectAll"); printCountryList(countryList); } finally { sqlSession.close(); } } private void printCountryList(List<Country> countryList) { for (Country country : countryList) { System.out.printf("%-4d%4s%4s\n", country.getId(), country.getCountryname(), country.getCountrycode()); } } }
修改mybatis-config.xml,把CountryMapper.xml加入到Mapper配置中
<mappers> <mapper resource="tk/mybatis/simple/mapper/CountryMapper.xml" /> <package name="tk.mybatis.simple.mapper" /> </mappers>
完成上面設置後便可進行單元測試
a、關聯查找某個表的數據
接口方法
/** * 根據用戶 id 獲取角色信息 * * @param userId * @return */ List<SysRole> selectRolesByUserId(Long userId);
XML配置
<select id="selectRolesByUserId" resultType="tk.mybatis.simple.model.SysRole"> select r.id, r.role_name roleName, r.enabled, r.create_by createBy, r.create_time createTime, u.user_name as "user.userName", u.user_email as "user.userEmail" from sys_user u inner join sys_user_role ur on u.id = ur.user_id inner join sys_role r on ur.role_id = r.id where u.id = #{userId} </select>
由於使用了自定義類型Enable,因此在mybatis-config.xml加入自定義類型處理器
<typeHandlers> <typeHandler javaType="tk.mybatis.simple.type.Enabled" handler="tk.mybatis.simple.type.EnabledTypeHandler"/> </typeHandlers>
這種狀況,雖然有關聯表查詢,可是隻是一個實體的數據,因此只是sql中加入了join語句的不一樣,其它和單個表查詢基本相同
b、關聯查詢多個表數據
例如a中,我不只要查詢角色,也要帶上用戶的信息,那就是要查詢出2個表的信息了,那麼能夠以下這麼作
第一種:把用戶的信息增長到角色表中;不過這種方式不建議
package tk.mybatis.simple.model; public class SysRoleExtend extends SysRole { private String userName; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
第二種:把user表加到角色表中
public class SysRole implements Serializable { /** * 用戶信息 */ private SysUser user; ......
修改mapper,使用「.」的方式增長user須要的信息
<select id="selectRolesByUserId" resultType="tk.mybatis.simple.model.SysRole"> select r.id, r.role_name roleName, r.enabled, r.create_by createBy, r.create_time createTime, u.user_name as "user.userName", u.user_email as "user.userEmail" from sys_user u inner join sys_user_role ur on u.id = ur.user_id inner join sys_role r on ur.role_id = r.id where u.id = #{userId} </select>
測試代碼
@Test public void testSelectRolesByUserId(){ SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //調用 selectRolesByUserId 方法查詢用戶的角色 List<SysRole> roleList = userMapper.selectRolesByUserId(1L); //結果不爲空 Assert.assertNotNull(roleList); //角色數量大於 0 個 Assert.assertTrue(roleList.size() > 0); } finally { //不要忘記關閉 sqlSession sqlSession.close(); } }
insert比較簡單,除了須要返回主鍵時,不一樣數據庫的方式有所不一樣
添加接口方法
/** * 新增用戶 * * @param sysUser * @return */ int insert(SysUser sysUser);
添加XML設置
<insert id="insert"> insert into sys_user( user_name, user_password, user_email, user_info, head_img, create_time) values( #{userName}, #{userPassword}, #{userEmail}, #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP}) </insert>
測試代碼
@Test public void testInsert() { SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 建立一個 user 對象 SysUser user = new SysUser(); user.setUserName("test1"); user.setUserPassword("123456"); user.setUserEmail("test@mybatis.tk"); user.setUserInfo("test info"); // 正常狀況下應該讀入一張圖片存到 byte 數組中 user.setHeadImg(new byte[] { 1, 2, 3 }); user.setCreateTime(new Date()); // 將新建的對象插入數據庫中,特別注意,這裏的返回值 result 是執行的 SQL 影響的行數 int result = userMapper.insert(user); // 只插入 1 條數據 Assert.assertEquals(1, result); // id 爲 null,咱們沒有給 id 賦值,而且沒有配置回寫 id 的值 Assert.assertNull(user.getId()); } finally { // 爲了避免影響數據庫中的數據致使其餘測試失敗,這裏選擇回滾 // 因爲默認的 sqlSessionFactory.openSession() 是不自動提交的, // 所以不手動執行 commit 也不會提交到數據庫 sqlSession.rollback(); // 不要忘記關閉 sqlSession sqlSession.close(); } }
<insert>標籤包含以下屬性:
適用於數據庫自己能夠設置字段爲自增的狀況
接口方法
/** * 新增用戶 - 使用 useGeneratedKeys 方式 * * @param sysUser * @return */ int insert2(SysUser sysUser);
XML配置,主要和上面例子增長了useGeneratedKeys="true" keyProperty="id"
<insert id="insert2" useGeneratedKeys="true" keyProperty="id"> insert into sys_user( user_name, user_password, user_email, user_info, head_img, create_time) values( #{userName}, #{userPassword}, #{userEmail}, #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP}) </insert>
useGeneratedKeys設置爲true後,MyBatis會使用JDBC的getGeneratedKeys方法來取出由數據庫內部生成的主鍵。得到主鍵值後將其賦值給keyProperty配置的id屬性。當須要設置多個屬性時,使用逗號隔開,這種狀況下一般還須要設置keyColumn屬性,按順序指定數據庫的列,這裏列的值會和keyProperty配置的屬性一一對應
測試代碼
@Test public void testInsert2() { SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 建立一個 user 對象 SysUser user = new SysUser(); user.setUserName("test1"); user.setUserPassword("123456"); user.setUserEmail("test@mybatis.tk"); user.setUserInfo("test info"); user.setHeadImg(new byte[] { 1, 2, 3 }); user.setCreateTime(new Date()); int result = userMapper.insert2(user); // 只插入 1 條數據 Assert.assertEquals(1, result); // 由於 id 回寫,因此 id 不爲 null Assert.assertNotNull(user.getId()); } finally { sqlSession.commit(); // 不要忘記關閉 sqlSession sqlSession.close(); } }
這種方式既適用於2的狀況,更適用於數據庫不支持自增列,而是經過序列獲得一個值,而後賦值給id再插入數據庫的狀況
接口方法
/** * 新增用戶 - 使用 selectKey 方式 * * @param sysUser * @return */ int insert3(SysUser sysUser);
XML配置
MySQL數據庫
<insert id="insert3"> insert into sys_user( user_name, user_password, user_email, user_info, head_img, create_time) values( #{userName}, #{userPassword}, #{userEmail}, #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP}) <selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> </insert>
Oracle數據庫
<!-- Oracle 的例子,查詢多個列的時候須要 keyColumn --> <insert id="insertOracle"> <selectKey keyColumn="id" resultType="long" keyProperty="id" order="BEFORE"> SELECT SEQ_USER.nextval from dual </selectKey> insert into sys_user( id, user_name, user_password, user_email, user_info, head_img, create_time) values( #{id}, #{userName}, #{userPassword}, #{userEmail}, #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP}) </insert>
selectKey標籤的keyColumn、keyProperty和上面useGeneratedKeys的用法含義相同,這裏的resultType用於設置返回值類型。order屬性的設置和使用的數據庫有關。在MySQL數據庫中,order屬性設置的值是AFTER,由於當前記錄的主鍵值在insert語句執行成功後才能獲取到。而在Oracle數據庫中,order的值要設置爲BEFORE,這是由於Oracle中須要先從序列獲取值,而後將值做爲主鍵插入到數據庫中
Oracle方式的INSERT語句中明確寫出了id列和值#{id},由於執行selectKey中的語句後id就有值了,咱們須要把這個序列值做爲主鍵值插入到數據庫中,因此必須指定id列,若是不指定這一列,數據庫就會由於主鍵不能爲空而拋出異常
而selectKey標籤的寫法在先後並沒有所謂,影響執行順序的只是order
不一樣數據庫生成id的語句不一樣
接口方法
/** * 根據主鍵更新 * * @param sysUser * @return */ int updateById(SysUser sysUser);
XML配置
<update id="updateById"> update sys_user set user_name = #{userName}, user_password = #{userPassword}, user_email = #{userEmail}, user_info = #{userInfo}, head_img = #{headImg, jdbcType=BLOB}, create_time = #{createTime, jdbcType=TIMESTAMP} where id = #{id} </update>
測試代碼
@Test public void testUpdateById() { SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 從數據庫查詢 1 個 user 對象 SysUser user = userMapper.selectById(1L); // 當前 userName 爲 admin Assert.assertEquals("admin", user.getUserName()); // 修改用戶名 user.setUserName("admin_test"); // 修改郵箱 user.setUserEmail("test@mybatis.tk"); // 更新數據,特別注意,這裏的返回值 result 是執行的 SQL 影響的行數 int result = userMapper.updateById(user); // 只更新 1 條數據 Assert.assertEquals(1, result); // 根據當前 id 查詢修改後的數據 user = userMapper.selectById(1L); // 修改後的名字 admin_test Assert.assertEquals("admin_test", user.getUserName()); } finally { // 爲了避免影響數據庫中的數據致使其餘測試失敗,這裏選擇回滾 // 因爲默認的 sqlSessionFactory.openSession() 是不自動提交的, // 所以不手動執行 commit 也不會提交到數據庫 sqlSession.rollback(); // 不要忘記關閉 sqlSession sqlSession.close(); } }
update就介紹到這裏,更復雜的就須要瞭解後面的動態sql用法了
接口方法
/** * 經過主鍵刪除 * * @param id * @return */ int deleteById(Long id); /** * 經過主鍵刪除 * * @param id * @return */ int deleteById(SysUser sysUser);
XML配置
<delete id="deleteById"> delete from sys_user where id = #{id} </delete>
測試代碼
@Test public void testDeleteById() { SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 從數據庫查詢 1 個 user 對象,根據 id = 1 查詢 SysUser user1 = userMapper.selectById(1L); // 如今還能查詢出 user 對象 Assert.assertNotNull(user1); // 調用方法刪除 Assert.assertEquals(1, userMapper.deleteById(1L)); // 再次查詢,這時應該沒有值,爲 null Assert.assertNull(userMapper.selectById(1L)); // 使用 SysUser 參數再作一遍測試,根據 id = 1001 查詢 SysUser user2 = userMapper.selectById(1001L); // 如今還能查詢出 user 對象 Assert.assertNotNull(user2); // 調用方法刪除,注意這裏使用參數爲 user2 Assert.assertEquals(1, userMapper.deleteById(user2)); // 再次查詢,這時應該沒有值,爲 null Assert.assertNull(userMapper.selectById(1001L)); // 使用 SysUser 參數再作一遍測試 } finally { // 爲了避免影響數據庫中的數據致使其餘測試失敗,這裏選擇回滾 // 因爲默認的 sqlSessionFactory.openSession() 是不自動提交的, // 所以不手動執行 commit 也不會提交到數據庫 sqlSession.rollback(); // 不要忘記關閉 sqlSession sqlSession.close(); } }
delete用法也只講到這裏,更復雜的可學習動態sql
在前面的例子,全部接口方法都只有一個參數,好比一個string類型的id,或者是一個JavaBean如SysUser
當要傳輸多個參數的時候,有2種好用的方法:一、使用Map類型做爲參數;二、使用@Param註解
使用Map類型做爲參數的方法,就是在Map中經過key來映射XML中SQL使用的參數值名字,value用來存放參數值,須要多個參數時,經過Map的key-value方式傳遞參數值,因爲這種方式還須要本身手動建立Map以及對參數進行賦值,其實並不簡潔,因此對這種方式只作以上簡單介紹,接下來着重講解使用@Param註解的方式
接口方法
/** * 根據用戶 id 和 角色的 enabled 狀態獲取用戶的角色 * * @param userId * @param enabled * @return */ List<SysRole> selectRolesByUserIdAndRoleEnabled(Long userId, Integer enabled);
XML配置
<select id="selectRolesByUserIdAndRoleEnabled" resultType="tk.mybatis.simple.model.SysRole"> select r.id, r.role_name roleName, r.enabled, r.create_by createBy, r.create_time createTime from sys_user u inner join sys_user_role ur on u.id = ur.user_id inner join sys_role r on ur.role_id = r.id where u.id = #{userId} and r.enabled = #{enabled} </select>
測試代碼
@Test public void testSelectRolesByUserIdAndRoleEnabled() { SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 調用 selectRolesByUserIdAndRoleEnabled 方法查詢用戶的角色 List<SysRole> roleList = userMapper.selectRolesByUserIdAndRoleEnabled(1L, null); // 結果不爲空 Assert.assertNotNull(roleList); // 角色數量大於 0 個 Assert.assertTrue(roleList.size() > 0); } finally { // 不要忘記關閉 sqlSession sqlSession.close(); } }
會報錯Parameter 'userId' not found. Available parameters are [0, 1, param1, param2]
這個錯誤表示,XML可用的參數只有0、一、param一、param2,沒有userId。0和1,param1和param2都是MyBatis根據參數位置自定義的名字,這時若是將XML中的#{userId}改成#{0}或#{param1},將#{enabled}改成#{1}或#{param2},這個方法就能夠被正常調用了。這樣講只是爲了讓你們理解它們之間的關係,但實際上並不建議這麼作
修改接口方法便可
/** * 根據用戶 id 和 角色的 enabled 狀態獲取用戶的角色 * * @param userId * @param enabled * @return */ List<SysRole> selectRolesByUserIdAndRoleEnabled(@Param("userId") Long userId, @Param("enabled") Integer enabled);
修改後能夠經過測試
這時的XML文件中對應的SQL的可用參數變成了[userId,enabled,param1,param2],若是把#{userId}改成#{param1},把#{enabled}改成#{param2},測試也能夠經過
給參數配置@Param註解後,MyBatis就會自動將參數封裝成Map類型,@Param註解值會做爲Map中的key,所以在SQL部分就能夠經過配置的註解值來使用參數。
到這裏你們可能會有一個疑問:當只有一個參數(基本類型或擁有TypeHandler配置的類型)的時候,爲何能夠不使用註解?這是由於在這種狀況下(除集合和數組外),MyBatis不關心這個參數叫什麼名字就會直接把這個惟一的參數值拿來使用。
當參數是JavaBean的時候,如
/** * 根據用戶 id 和 角色的 enabled 狀態獲取用戶的角色 * * @param user * @param role * @return */ List<SysRole> selectRolesByUserAndRole(@Param("user")SysUser user, @Param("role")SysRole role);
這時,在XML中就不能直接使用#{userId}和#{enabled}了,而是要經過點取值方式使用#{user.id}和#{role.enabled}從兩個JavaBean中取出指定屬性的值。修改好對應的XML文件後,你們能夠自行完善代碼並進行測試。
除了以上經常使用的參數類型外,接口的參數還多是集合或者數組,這種類型的參數暫不討論
使用ORM的人都知道,根據不一樣條件拼裝SQL時會遇到不少麻煩,例如缺乏空格、去掉最後列名的逗號等問題
MyBatis採用 OGNL(Object-Graph Navigation Language)表達式實現動態SQL,更加簡便
根據標籤中的test表達式(下面會詳細講解),若是符合表達式,則拼接在語句中;不然忽略
<select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap"> select * from Product where 1=1 <if test="name != null and name != ''"> and Name like '%'+#{name}+'%' </if> <if test="code != null and code != ''"> and Code like '%'+#{code}+'%' </if> order by code </select>
注意:where 1=1 :當2個條件都沒有時,若是不加1=1,會產生語句「select * from Product where」,是一個錯誤語句;可使用Where標籤解決(下面會講解)
<update id="updateTestIf"> update Product set <if test="name != null and name != ''"> Name = #{name}, </if> <if test="brand != null and brand != ''"> Brand = #{brand}, </if> Code = #{code} where Code = #{code} </update>
注意:賦值Code = #{code},也是爲了防止拼裝出來的語句有誤;可使用Where標籤或者Set標籤解決(下面會講解)
動態插入,若是實體的值爲空,則不插入,這樣可讓字段的值使用數據庫的默認值,而不是實體中帶的空值。
<update id="insertTestIf" parameterType="com.LTSolution.ShopApp.Model.Product"> insert into Product( Code,TypeId, <if test="brand != null and brand != ''"> Brand, </if> Name) values( #{code},#{typeid}, <if test="brand != null and brand != ''"> #{brand}, </if> #{name} ) </update>
注意,字段和數值都必須有對應的if,不然會報致使數值數量和字段數量不一致
此標籤提供了相似if...else...的功能;在此標籤中必須至少包含一個when,0或1個otherwise
<select id="selectTestChoose" resultMap="BaseResultMap"> select * from Product where 1=1 <choose> <when test="code != null and code != ''"> and Code = #{code} </when> <when test="name != null and name != ''"> and Name = #{name} </when> <otherwise> and 1=2 </otherwise> </choose> order by code </select>
若是有編號,按編號查找;沒有編號,按名稱查找;都沒有則查不到。
他們都用來解決相似拼裝時是否去掉第一個and,是否去掉最後一個逗號等問題
where 標籤的做用:若標籤中有內容,則自動插入一個where,不然不插入任何語句 ;若是 where 後面的字符串是以 AND 和 OR 開頭的,就將它們剔除。
修改上面if標籤中where例子,便可省掉where 1=1這個語句
<select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap"> select * from Product <where> <if test="name != null and name != ''"> and Name like '%'+#{name}+'%' </if> <if test="code != null and code != ''"> and Code = #{code} </if> </where> order by code </select>
set標籤的做用:若標籤中有內容,則自動插入一個set ;若是 set 後面的字符串是以逗號結尾的,就將這個逗號剔除。
修改上面if標籤中update例子以下
<update id="updateTestIf"> update Product <set> <if test="name != null and name != ''"> Name = #{name}, </if> <if test="brand != null and brand != ''"> Brand = #{brand}, </if> Code = #{code}, </set> where Code = #{code} </update>
where和set標籤的功能均可以用trim標籤來實現,而且在底層就是經過TrimSqlNode實現的
用trim標籤實現where和set
<select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap"> select * from Product <trim prefix="WHERE" prefixOverrides="AND |OR "> <if test="name != null and name != ''"> and Name like '%'+#{name}+'%' </if> <if test="code != null and code != ''"> and Code = #{code} </if> </trim> order by code </select>
這裏的AND和OR後面的空格不能省略,爲了不匹配到andes、orders等單詞。
實際的prefixeOverrides包含「AND」、「OR」、「AND\n」、「OR\n」、「AND\r」、「OR\r」、「AND\t」、「OR\t」,不只僅是上面提到的兩個帶空格的前綴。
<update id="updateTestIf"> update Product <trim prefix="SET" suffixOverrides=","> <if test="name != null and name != ''"> Name = #{name}, </if> <if test="brand != null and brand != ''"> Brand = #{brand}, </if> Code = #{code}, </trim> where Code = #{code} </update>
SQL語句中有時會使用IN關鍵字,例如id in(1,2,3)。使用${ids}方式直接獲取值的寫法不能防止SQL注入;使用#{}加上foreach標籤的寫法,能夠避免SQL注入。
foreach能夠對數組、Map或實現了Iterable接口(如List、Set)的對象進行遍歷。
數組在處理時會轉換爲List對象,所以foreach遍歷的對象能夠分爲兩大類:Iterable類型和Map類型。
這兩種類型在遍歷循環時狀況不同,例子以下:
例子:使用傳入的編號集合查找對應商品
//接口 List<Product> selectTestForeach(List<String> idList);
<select id="selectTestForeach" resultMap="BaseResultMap"> select * from Product where code in <foreach collection="list" open="(" close=")" separator="," item="id" index="i"> #{id} </foreach> order by code </select>
collection的設置:
若是數據庫支持批量插入,就能夠經過foreach來實現。批量插入是SQL-92新增的特性,目前支持的數據庫有DB二、SQLServer2008及以上版本、PostgreSQL8.2及以上版本、MySQL、SQLite3.7.11及以上版本、H2。
sql語法:insert into table1 (c1,c2,c3..) values (v1,v2,v3..),(v1,v2,v3..),(v1,v2,v3..)
<update id="insertTestForeach"> insert into Product(Code,TypeId,Name) values <foreach collection="list" item="product" separator=","> (#{product.code},#{product.typeid},#{product.name}) </foreach> </update>
經過item指定了循環變量名後,在引用值的時候使用的是「屬性.屬性」的方式,如product.code
批量插入還能夠返回自增主鍵id,這裏不介紹
介紹當參數類型是Map的時候,如何用foreach進行動態update
// 接口 int updateTestForeach(Map<String, Object> map);
<update id="updateTestForeach"> update Product set <foreach collection="_parameter" item="val" index="key" separator=","> ${key} = #{val} </foreach> where Code = #{code} </update>
bind標籤可使用OGNL表達式建立一個變量並綁定到上下文中
例如鏈接函數concat,不一樣數據庫用法不一樣,能夠改爲用bind標籤的OGNL來表示,便可消除不一樣數據庫不一樣寫法的影響,並且能夠防止sql注入
bind標籤有2個屬性,name爲給上下文引用的變量名,value爲OGNL表達式
<select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap"> select * from Product <where> <if test="name != null and name != ''"> <bind name="nameLike" value="'%'+name+'%'" /> and Name like #{nameLike} </if> </where> order by code </select>
使用if標籤以及由MyBatis提供的databaseIdProvider數據庫廠商標識配置
MyBatis根據映射語句中的databaseId屬性,對不一樣的數據庫執行對應的語句;MyBatis會加載不帶databaseId屬性以及匹配當前數據庫databaseId屬性的語句;若是同時找到同id,不帶databaseId屬性和帶databaseId屬性的語句,不帶databaseId屬性的語句會被捨棄掉。
首先,須要在mybatis-config.xml文件中加入配置
<databaseIdProvider type="DB_VENDOR"/>
在這個設置下,系統經過DatabaseMetaData#getDatabaseProductName()方法獲取數據庫名稱,可是根據不一樣數據庫版本,返回的名稱會不一樣,比較難管理,因此有如下方式
<databaseIdProvider type="DB_VENDOR"> <property name="SQL Server" value="sqlserver"/> <property name="DB2" value="db2"/> <property name="Oracle" value="oracle" /> <property name="MySQL" value="mysql"/> <property name="PostgreSQL" value="postgresql"/> <property name="Derby" value="derby"/> <property name="HSQL" value="hsqldb"/> <property name="H2" value="h2"/> </databaseIdProvider>
經過配置,只要DatabaseMetaData#getDatabaseProductName()的返回值部分字符匹配某個屬性的name,就會返回對應的value值
注意!Spring Boot中不可使用使用配置文件的形式,應該添加一個Bean
package com.LTSolution.ShopApp; import java.util.Properties; import org.apache.ibatis.mapping.DatabaseIdProvider; import org.apache.ibatis.mapping.VendorDatabaseIdProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyBatisBean { @Bean public DatabaseIdProvider getDatabaseIdProvider() { DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); Properties properties = new Properties(); properties.setProperty("Oracle", "oracle"); properties.setProperty("MySQL", "mysql"); properties.setProperty("DB2", "db2"); properties.setProperty("Derby", "derby"); properties.setProperty("H2", "h2"); properties.setProperty("HSQL", "hsql"); properties.setProperty("Informix", "informix"); properties.setProperty("SQL Server", "sqlserver"); properties.setProperty("PostgreSQL", "postgresql"); properties.setProperty("Sybase", "sybase"); properties.setProperty("Hana", "hana"); databaseIdProvider.setProperties(properties); return databaseIdProvider; } }
方式1:對select,insert,delete,update,selectKey,sql標籤,設置databaseId屬性。MyBatis會根據數據庫執行對應的語句
<select id="selectTestIf" databaseId="mysql"> .... </select> <select id="selectTestIf" databaseId="sqlserver"> .... </select>
方式2:使用_databaseId參數。爲了不大量重複的SQL,可使用_databaseId參數進行對不一樣的部分進行判斷。
<select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap"> select * from Product <where> <if test="name != null and name != ''"> <if test="_databaseId == 'mysql'"> and Name like concat('%'+#{name}+'%') </if> <if test="_databaseId == 'sqlserver'"> and Name like '%'+#{name}+'%' </if> </if> </where> order by code </select>
在MyBatis的動態SQL和${}形式的參數中都用到了OGNL表達式。MyBatis經常使用的OGNL表達式以下:
1.e1 or e2 2.e1 and e2 3.e1==e2或e1 eq e2 4.e1 !=e2或e1 neq e2 5.e1 lt e2:小於 6.e1 lte e2:小於等於,其餘表示爲gt(大於)、gte(大於等於) 7.e1+e二、e1*e二、e1/e二、e1-e二、e1%e2 8.!e或not e:非,取反 9.e.method( args ):=調用對象方法 10.e.property:對象屬性值 11.e1[e2]:按索引取值( List、數組和Map) 12.@class@method(args):調用類的靜態方法 13.@class@field:調用類的靜態字段值
MyBatis容許在已映射語句執行過程當中的某一點進行攔截調用。默認狀況下,MyBatis能夠攔截一下4個接口中的幾個方法(括號內爲該接口的方法名)。
這些都是比較底層的類和方法,修改得很差,可能會影響代碼執行。因此攔截調用前必定要充分理解。
MyBatis插件經過實現攔截器接口Interceptor(org.apache.ibatis.plugin.Interceptor),便可在實現類中對攔截對象和方法進行處理。
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }
一、setProperties
二、plugin
三、intercept
經過攔截器註解,來代表這個攔截器須要攔截的接口和方法
@Intercepts(org.apache.ibatis.plugin.Intercepts)和@Signature(org.apache.ibatis.plugin.Signature)
@Intercepts註解中的屬性是一個@Signature(簽名)數組,能夠在同一個攔截器中同時攔截不一樣的接口和方法。
@Signature註解包含如下三個屬性。
type:設置攔截的接口,可選值是前面提到的4個接口。
method:設置攔截接口中的方法名,可選值是前面4個接口對應的方法,須要和接口匹配。
args:設置攔截方法的參數類型數組,經過方法名和參數類型能夠肯定惟一一個方法。
type:接口類
method:方法名稱
args:要攔截的方法的參數類
下面例子爲Executor的update方法的簽名寫法
@Intercepts( @org.apache.ibatis.plugin.Signature( type = Executor.class, method = "update", args = { MappedStatement.class,Object.class })) public class ResultSetInterceptor implements Interceptor {
int update(MappedStatement ms,Object parameter) throws SQLException
該方法會在全部的 INSERT 、 UPDATE 、 DELETE 執行時被調用,所以若是想要攔截這 3 類操做,能夠攔截該方法
<E>List<E>query(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler)throwsSQLException
該方法會在全部SELECT查詢方法執行時被調用。經過這個接口參數能夠獲取不少有用的信息,所以這是最常被攔截的一個方法。使用該方法須要注意的是,雖然接口中還有一個參數更多的同名接口,但因爲MyBatis的設計緣由,這個參數多的接口不能被攔截
<E> Cursor <E> queryCursor(MappedStatement ms,Object parameter,RowBounds rowBounds) throws SQLException
該方法只有在查詢的返回值類型爲 Cursor 時被調用。
List <BatchResult > flushStatements() throws SQLException
該方法只在經過 SqlSession 方法調用 flushStatements 方法或執行的接口方法中帶有 @Flush 註解時才被調用,
void commit(boolean required) throws SQLException
該方法只在經過 SqlSession 方法調用 commit 方法時才被調用,
void rollback(boolean required) throws SQLException
該方法只在經過 SqlSession 方法調用 rollback 方法時才被調用,
Transaction getTransaction()
該方法只在經過 SqlSession 方法獲取數據庫鏈接時才被調用,
void close(boolean forceRollback)
該方法只在延遲加載獲取新的 Executor 後纔會被執行,
boolean isClosed()
該方法只在延遲加載執行查詢方法前被執行,
Object getParameterObject()
該方法只在執行存儲過程處理出參的時候被調用。
void setParameters(PreparedStatement ps) throws SQLException
該方法在全部數據庫方法設置 SQL 參數時被調用。
<E> List <E> handleResultSets(Statement stmt) throws SQLException
該方法會在除存儲過程及返回值類型爲Cursor<T>(org.apache.ibatis.cursor.Cursor<T>)之外的查詢方法中被調用。
<E> Cursor <E> handleCursorResultSets(Statement stmt) throws SQLException;
該方法是 3.4.0 版本中新增長的,只會在返回值類型爲 Cursor <T>的查詢方法中被調用
void handleOutputParameters(CallableStatement cs) throws SQLException;
該方法只在使用存儲過程處理出參時被調用,ResultSetHandler接口的第一個方法對於攔截處理MyBatis的查詢結果很是有用,而且因爲這個接口被調用的位置在處理二級緩存以前,所以經過這種方式處理的結果能夠執行二級緩存。
Statement prepare(Connection connection,Integer transactionTimeout) throws SQLException
該方法會在數據庫執行前被調用,優先於當前接口中的其餘方法而被執行
void parameterize(Statement statement) throws SQLException
該方法在prepare方法以後執行,用於處理參數信息
int batch(Statement statement) throws SQLException
在全局設置配置defaultExecutorType="BATCH"時,執行數據操做纔會調用該方法
<E> List <E> query(Statement statement,ResultHandler resultHandler) throws SQLException
執行SELECT方法時調用
<E> Cursor <E> queryCursor(Statement statement) throws SQLException
該方法是 3.4.0 版本中新增長的,只會在返回值類型爲Cursor<T>的查詢中被調用
Spring Boot下須要加入依賴
<plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin>
此用法比較底層,建議使用基於接口的用法。
本例子用Eclipse+Maven來建立這個實例,請先掌握Maven的相關知識
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>tk.mybatis</groupId> <artifactId>simple</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>1.6</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> </plugins> </build> </project>
CREATE DATABASE mybatis DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; DROP TABLE IF EXISTS `country`; CREATE TABLE `country` ( `id` int(11) NOT NULL AUTO_INCREMENT, `countryname` varchar(255) DEFAULT NULL, `countrycode` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; INSERT INTO `country` VALUES ('1', '中國', 'CN'); INSERT INTO `country` VALUES ('2', '美國', 'US'); INSERT INTO `country` VALUES ('3', '俄羅斯', 'RU'); INSERT INTO `country` VALUES ('4', '英國', 'GB'); INSERT INTO `country` VALUES ('5', '法國', 'FR');
src/main/resources下建立mybatis-config.xml,注意修改MySql的ip地址和賬號密碼
<?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> <settings> <setting name="logImpl" value="LOG4J"/> </settings> <typeAliases> <package name="tk.mybatis.simple.model"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="" value=""/> </transactionManager> <dataSource type="UNPOOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.16.137:3306/mybatis"/> <property name="username" value="root"/> <property name="password" value=""/> </dataSource> </environment> </environments> <mappers> <mapper resource="tk/mybatis/simple/mapper/CountryMapper.xml"/> </mappers> </configuration>
<settings> 中的 logImpl 屬性配置指定使用 LOG4J 輸出日誌
<typeAliases> 元素下面配置了一個包的別名,一般肯定一個類的時候須要使用類的全限定名稱,例如 tk.mybatis.simple.model.Country 。在 MyBatis 中須要頻繁用到類的全限定名稱,爲了方便使
用,咱們配置了 tk.mybatis.simple.model 包,這樣配置後,在使用類的時候不須要寫包名的部分,只使用 Country 便可。
<environments> 環境配置中主要配置了數據庫鏈接,數據庫的 url 爲 jdbc:mysql://localhost:3306/mybatis ,使用的是本機 MySQL 中的 mybatis 數據庫,後面的 username 和 password 分別是數據庫
的用戶名和密碼(若是你的數據庫用戶名及密碼和這裏的不同,請修改成本身數據庫可用的用戶名和密碼)
<mappers> 中配置了一個包含完整類路徑的 CountryMapper.xml ,這是一個 MyBatis 的 SQL 語句和映射配置文件
在src/main/java下建立包tk.mybatis.simple.model
而後建立一個類
package tk.mybatis.simple.model; public class Country { private Long id; private String countryname; private String countrycode; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getCountryname() { return countryname; } public void setCountryname(String countryname) { this.countryname = countryname; } public String getCountrycode() { return countrycode; } public void setCountrycode(String countrycode) { this.countrycode = countrycode; } }
在src/main/resources下面建立tk/mybatis/simple/mapper目錄,再在該目錄下面建立CountryMapper.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="tk.mybatis.simple.mapper.CountryMapper"> <select id="selectAll" resultType="Country"> select id,countryname,countrycode from country </select> </mapper>
SQL 定義在 CountryMapper.xml 文件中,裏面的配置做用以下。
<mapper>: XML 的根元素,屬性 namespace 定義了當前 XML 的命名空間。
<select>元素:咱們所定義的一個 SELECT 查詢。
id 屬性:定義了當前 SELECT 查詢的惟一一個 id 。
resultType :定義了當前查詢的返回值類型,此處就是指實體類 Country ,前面配置中提到的別名主要用於這裏,若是沒有設置別名,此處就須要寫成 resultType= " tk.mybatis.simple.model.Country
"。
· select id , ... :查詢 SQL 語句。
在src/main/resources中添加log4j.properties配置文件
#\u5168\u5C40\u914D\u7F6E log4j.rootLogger=ERROR, stdout #MyBatis \u65E5\u5FD7\u914D\u7F6E log4j.logger.tk.mybatis.simple.mapper=TRACE #\u63A7\u5236\u53F0\u8F93\u51FA\u914D\u7F6E log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
日誌注意事項!
用過 Log4j 日誌組件的人可能都會知道,配置中的 log4j.logger.tk.mybatis.simple.mapper 對應的是 tk.mybatis.simple.mapper 包,可是在這個例子中, Java 目錄下並無這個包名,只在資源目錄下有
mapper 目錄。
在 MyBatis 的日誌實現中,所謂的包名其實是 XML 配置中的 namespace 屬性值的一部分。後面章節中介紹結合接口使用的相關內容時,因爲 namespace 屬性值必須和接口全限定類名相同,所以
纔會真正對應到 Java 中的包。當使用純註解方式時,使用的就是純粹的包名。
MyBatis 日誌的最低級別是 TRACE ,在這個日誌級別下, MyBatis 會輸出執行 SQL 過程當中的詳細信息,這個級別特別適合在開發時使用。
首先在 src/test/java 中建立 tk.mybatis.simple.mapper 包,而後建立 CountryMapperTest 測試類
package tk.mybatis.simple.mapper; import java.io.IOException; import java.io.Reader; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.BeforeClass; import org.junit.Test; import tk.mybatis.simple.model.Country; public class CountryMapperTest { private static SqlSessionFactory sqlSessionFactory; @BeforeClass public static void init(){ try { Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); reader.close(); } catch (IOException ignore) { ignore.printStackTrace(); } } @Test public void testSelectAll(){ SqlSession sqlSession = sqlSessionFactory.openSession(); try { List<Country> countryList = sqlSession.selectList("selectAll"); printCountryList(countryList); } finally { sqlSession.close(); } } private void printCountryList(List<Country> countryList){ for(Country country : countryList){ System.out.printf("%-4d%4s%4s\n",country.getId(), country.getCountryname(), country.getCountrycode()); } } }
對上面這段代碼作一個簡單的說明,具體以下。
· 經過 Resources 工具類將 mybatis-config.xml 配置文件讀入 Reader 。
· 再經過 SqlSessionFactoryBuilder 建造類使用 Reader 建立 SqlSessionFactory 工廠對象。在建立 SqlSessionFactory 對象的過程當中,首先解析 mybatis-config.xml 配置文件,讀取配置文件中的 mappers 配置後會
讀取所有的 Mapper.xml 進行具體方法的解析,在這些解析完成後, SqlSessionFactory 就包含了全部的屬性配置和執行 SQL 的信息。
· 使用時經過 SqlSessionFactory 工廠對象獲取一個 SqlSession 。
· 經過 SqlSession 的 selectList 方法查找到 CountryMapper.xml 中 id= " selectAll "的方法,執行 SQL 查詢。
· MyBatis 底層使用 JDBC 執行 SQL ,得到查詢結果集 ResultSet 後,根據 resultType 的配置將結果映射爲 Country 類型的集合,返回查詢結果。
· 這樣就獲得了最後的查詢結果 countryList ,簡單將結果輸出到控制檯。
· 最後必定不要忘記關閉 SqlSession ,不然會由於鏈接沒有關閉致使數據庫鏈接數過多,形成系統崩潰。
在testSelectAll方法上,右鍵,[Run as]->[JUnit],便可看到輸出結果
DEBUG [main] - ==> Preparing: select id,countryname,countrycode from country DEBUG [main] - ==> Parameters: TRACE [main] - <== Columns: id, countryname, countrycode TRACE [main] - <== Row: 1, 中國, CN TRACE [main] - <== Row: 2, 美國, US TRACE [main] - <== Row: 3, 俄羅斯, RU TRACE [main] - <== Row: 4, 英國, GB TRACE [main] - <== Row: 5, 法國, FR DEBUG [main] - <== Total: 5 1 中國 CN 2 美國 US 3 俄羅斯 RU 4 英國 GB 5 法國 FR
至此,例子完成
MyBatis的配置方式有如下幾種:一、使用XML文件配置;二、使用Spring Bean配置;三、使用Java編碼方式配置
這裏僅介紹最經常使用的XML文件配置方式
一、在src/main/resources下建立mybatis-config.xml
二、該XML文件的通常格式
<?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> <settings> <!-- 當查詢結果字段未null時,是否調用該字段的setter方法 --> <setting name="callSettersOnNulls" value="true" /> </settings> <!--爲類型建立別名 --> <typeAliases> <typeAlias alias="Integer" type="java.lang.Integer" /> <typeAlias alias="Long" type="java.lang.Long" /> <typeAlias alias="HashMap" type="java.util.HashMap" /> <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" /> <typeAlias alias="ArrayList" type="java.util.ArrayList" /> <typeAlias alias="LinkedList" type="java.util.LinkedList" /> </typeAliases> <!--插件 --> <plugins> <!-- PageHelper4.1.1 --> <!--<plugin interceptor="com.github.pagehelper.PageHelper"> </plugin> --> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 這裏能夠對pagehelper進行配置,分別輸入參數名和參數的值,能夠查看相關文檔--> <property name="param1" value="value1" /> </plugin> </plugins> </configuration>
更詳盡的配置能夠查看:http://www.mybatis.org/mybatis-3/zh/configuration.html
須要用括號把top後面的動態參數括起來
select top (#{pageSize}) from ...
MyBatis Generator有如下幾種運行方式:
a、Java代碼
b、命令提示行
c、Maven插件
d、Eclipse插件
其中只有Eclipse插件支持代碼合併,能夠生成新代碼的同時保留本身添加的代碼。
a、安裝
下載插件: https:/github.com/mybatis/generator/releases
b、Eclipse中安裝插件
選擇下載的壓縮包,而後一直下一步到完成,而後重啓Eclipse,即完成了安裝。
src/main/resources下添加一個文件夾mybatis-generator,添加一個配置文件generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <classPathEntry location="C:\Softs\maven\Repository\com\microsoft\sqlserver\sqljdbc4\4.0\sqljdbc4-4.0.jar"/> <context id="SqlServerContext" targetRuntime="MyBatis3" defaultModelType="flat"> <property name="javaFileEncoding" value="UTF-8" /> <property name="autoDelimitKeywords" value="true" /> <property name="beginningDelimiter " value="[" /> <property name="endingDelimiter " value="]" /> <commentGenerator> <property name="suppressDate" value="true"/> <property name="addRemarkComments" value="true"/> </commentGenerator> <jdbcConnection driverClass="com.microsoft.sqlserver.jdbc.SQLServerDriver" connectionURL="jdbc:sqlserver://127.0.0.1;database=db" userId="sa" password="psw"> </jdbcConnection> <javaModelGenerator targetPackage="com.ltsolution.framework.bs.system.model" targetProject="framework\src\main\java"> <property name="trimStrings" value="true" /> </javaModelGenerator> <sqlMapGenerator targetPackage="mybatis.mapper" targetProject="framework\src\main\resources"/> <javaClientGenerator type="XMLMAPPER" targetPackage="com.ltsolution.framework.bs.system.mapper" targetProject="framework\src\main\java"/> <table tableName="Company"> <generatedKey column="RowId" sqlStatement="SQLServer" identity="true"/> </table> <table tableName="Customer"> <generatedKey column="RowId" sqlStatement="JDBC" identity="true"/> <columnOverride column="RowId" isGeneratedAlways="true"></columnOverride> </table> <table tableName="CustomerType"> <generatedKey column="RowId" sqlStatement="JDBC" identity="true"/> <columnOverride column="RowId" isGeneratedAlways="true"></columnOverride> </table> <table tableName="Product"> <generatedKey column="RowId" sqlStatement="JDBC" identity="true"/> <columnOverride column="RowId" isGeneratedAlways="true"></columnOverride> </table> <table tableName="ProductType"> <generatedKey column="RowId" sqlStatement="JDBC" identity="true"/> <columnOverride column="RowId" isGeneratedAlways="true"></columnOverride> </table> <table tableName="SaleShipmentDetail"> <generatedKey column="RowId" sqlStatement="SQLServer" identity="true"/> </table> <table tableName="SaleShipmentHead"> <generatedKey column="RowId" sqlStatement="SQLServer" identity="true"/> <ignoreColumn column="RowId" /> </table> <table tableName="Storage"> <generatedKey column="RowId" sqlStatement="SQLServer" identity="true"/> </table> <table tableName="HeadNoPrefix"> <generatedKey column="RowId" sqlStatement="SQLServer" identity="true"/> <ignoreColumn column="RowId" /> </table> <table tableName="StorageQuantity"> <generatedKey column="RowId" sqlStatement="SQLServer" identity="true"/> <ignoreColumn column="RowId" /> </table> </context> </generatorConfiguration>
主要修改幾個地方
classPathEntry:設置數據庫驅動的jar包位置
context-jdbcConnection:設置數據庫信息
context-javaModelGenerator:設置model的生成位置
context-sqlMapGenerator:設置mapper.xml文件的生成位置
context-javaClientGenerator:設置mapper接口的生成位置
關於在SQL中使用的幾個注意點,能夠查看https://www.cnblogs.com/LiveYourLife/p/9000874.html這篇文章
其它的自行查閱詳細資料
便可看到生成的結果
使用SQLServer數據庫
有2個表Customer、CustomerType,都有字段TypeId
後臺SQL:select A.Name ,A.TypeId AS CTypeId, B.TypeName FROM Customer A LEFT JOIN CustomerType B ON A.TypeId = B.TypeId
傳給前臺的字段是:Name,CTypeId,TypeName
前臺要求查詢條件:要求按CTypeId字段排序,而且分頁查詢1~3條記錄
有2個解決方式:
a、【此方法效率可能較低】使用子查詢,先把整個表查出來,而後按照TypeName排序並分頁(PageHelper的5.1.3版本是這樣實現的)
生成sql以下,一個由3個查詢組成
SELECT TOP 3 Name,CTypeId,TypeName FROM ( SELECT ROW_NUMBER() OVER (ORDER BY CTypeId DESC) PAGE_ROW_NUMBER, Name, CTypeId, TypeName FROM ( SELECT A.Name, A.TypeId AS CTypeId ,B.TypeName FROM Customer A LEFT JOIN CustomerType B ON A.TypeId = B.TypeId ) AS PAGE_TABLE_ALIAS ) AS PAGE_TABLE_ALIAS WHERE PAGE_ROW_NUMBER > 0 ORDER BY PAGE_ROW_NUMBER
b、【個人解決方案】將前臺傳過來的字段,解析成對應字段(上面例子中,將TypeName解析成A.name),插入到語句中,便可獲得分頁結果
對應的sql,節省了一個查詢
SELECT TOP 3 Name,CTypeId,TypeName FROM ( SELECT ROW_NUMBER() OVER (ORDER BY A.TypeId DESC) PAGE_ROW_NUMBER, A.Name, A.TypeId AS CTypeId ,B.TypeName FROM Customer A LEFT JOIN CustomerType B ON A.TypeId = B.TypeId ) AS PAGE_TABLE_ALIAS WHERE PAGE_ROW_NUMBER > 0 ORDER BY PAGE_ROW_NUMBER
一、使用5.1.3版本的源碼
二、修改SqlServer對應的解析器
這個解析器我是從舊的版本(不記得是否是4.1.7)基礎上修改的,那個版本還未支持上面第一種方式的排序分頁,因此當時是不能排序並分頁的
主要修改了addRowNumber方法,整個文件代碼以下
/* * The MIT License (MIT) * * Copyright (c) 2014 abel533@gmail.com * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.github.pagehelper.parser; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.github.pagehelper.PageException; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.operators.relational.GreaterThan; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.select.AllColumns; import net.sf.jsqlparser.statement.select.AllTableColumns; import net.sf.jsqlparser.statement.select.FromItem; import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.LateralSubSelect; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.SelectBody; import net.sf.jsqlparser.statement.select.SelectExpressionItem; import net.sf.jsqlparser.statement.select.SelectItem; import net.sf.jsqlparser.statement.select.SetOperationList; import net.sf.jsqlparser.statement.select.SubJoin; import net.sf.jsqlparser.statement.select.SubSelect; import net.sf.jsqlparser.statement.select.Top; import net.sf.jsqlparser.statement.select.ValuesList; import net.sf.jsqlparser.statement.select.WithItem; /** * 將sqlserver查詢語句轉換爲分頁語句<br> * 注意事項:<br> * <ol> * <li>請先保證你的SQL能夠執行</li> * <li>sql中最好直接包含order by,能夠自動從sql提取</li> * <li>若是沒有order by,能夠經過入參提供,可是須要本身保證正確</li> * <li>若是sql有order by,能夠經過orderby參數覆蓋sql中的order by</li> * <li>order by的列名不能使用別名</li> * <li>表和列使用別名的時候不要使用單引號(')</li> * </ol> * 該類設計爲一個獨立的工具類,依賴jsqlparser,能夠獨立使用 * * @author liuzh */ public class SqlServerParser { // 緩存結果 protected static final Map<String, String> CACHE = new ConcurrentHashMap<String, String>(); // 開始行號 protected static final String START_ROW = String.valueOf(Long.MIN_VALUE); // 結束行號 protected static final String PAGE_SIZE = String.valueOf(Long.MAX_VALUE); // 外層包裝表 protected static final String WRAP_TABLE = "WRAP_OUTER_TABLE"; // 表別名名字 protected static final String PAGE_TABLE_NAME = "PAGE_TABLE_ALIAS"; // protected public static final Alias PAGE_TABLE_ALIAS = new Alias(PAGE_TABLE_NAME); // 行號 protected static final String PAGE_ROW_NUMBER = "PAGE_ROW_NUMBER"; // 行號列 protected static final Column PAGE_ROW_NUMBER_COLUMN = new Column(PAGE_ROW_NUMBER); // TOP 100 PERCENT protected static final Top TOP100_PERCENT; // 靜態方法處理 static { TOP100_PERCENT = new Top(); TOP100_PERCENT.setExpression(new LongValue(100)); TOP100_PERCENT.setPercentage(true); } /** * 轉換爲分頁語句 * * @param sql * @param offset * @param limit * @return */ public String convertToPageSql(String sql, int offset, int limit) { String pageSql = CACHE.get(sql); if (pageSql == null) { // 解析SQL Statement stmt; try { stmt = CCJSqlParserUtil.parse(sql); } catch (Throwable e) { throw new RuntimeException("不支持該SQL轉換爲分頁查詢!"); } if (!(stmt instanceof Select)) { throw new RuntimeException("分頁語句必須是Select查詢!"); } // 獲取分頁查詢的select Select pageSelect = getPageSelect((Select) stmt); pageSql = pageSelect.toString(); CACHE.put(sql, pageSql); } pageSql = pageSql.replace(START_ROW, String.valueOf(offset)); pageSql = pageSql.replace(PAGE_SIZE, String.valueOf(limit)); return pageSql; } /** * 獲取一個外層包裝的TOP查詢 * * @param select * @return */ protected Select getPageSelect(Select select) { SelectBody selectBody = select.getSelectBody(); if (selectBody instanceof SetOperationList) { selectBody = wrapSetOperationList((SetOperationList) selectBody); } // 這裏的selectBody必定是PlainSelect if (((PlainSelect) selectBody).getTop() != null) { throw new RuntimeException("被分頁的語句已經包含了Top,不能再經過分頁插件進行分頁查詢!"); } // 獲取查詢列 List<SelectItem> selectItems = getSelectItems((PlainSelect) selectBody); // 對一層的SQL增長ROW_NUMBER() addRowNumber((PlainSelect) selectBody); // 處理子語句中的order by processSelectBody(selectBody, 0); // 新建一個select Select newSelect = new Select(); PlainSelect newSelectBody = new PlainSelect(); // 設置top Top top = new Top(); top.setExpression(new LongValue(Long.MAX_VALUE)); newSelectBody.setTop(top); // 設置order by List<OrderByElement> orderByElements = new ArrayList<OrderByElement>(); OrderByElement orderByElement = new OrderByElement(); orderByElement.setExpression(PAGE_ROW_NUMBER_COLUMN); orderByElements.add(orderByElement); newSelectBody.setOrderByElements(orderByElements); // 設置where GreaterThan greaterThan = new GreaterThan(); greaterThan.setLeftExpression(PAGE_ROW_NUMBER_COLUMN); greaterThan.setRightExpression(new LongValue(Long.MIN_VALUE)); newSelectBody.setWhere(greaterThan); // 設置selectItems newSelectBody.setSelectItems(selectItems); // 設置fromIterm SubSelect fromItem = new SubSelect(); fromItem.setSelectBody(selectBody); fromItem.setAlias(PAGE_TABLE_ALIAS); newSelectBody.setFromItem(fromItem); newSelect.setSelectBody(newSelectBody); if (isNotEmptyList(select.getWithItemsList())) { newSelect.setWithItemsList(select.getWithItemsList()); } return newSelect; } /** * 包裝SetOperationList * * @param setOperationList * @return */ protected SelectBody wrapSetOperationList(SetOperationList setOperationList) { // 獲取最後一個plainSelect SelectBody setSelectBody = setOperationList.getSelects().get(setOperationList.getSelects().size() - 1); if (!(setSelectBody instanceof PlainSelect)) { throw new RuntimeException("目前沒法處理該SQL,您能夠將該SQL發送給abel533@gmail.com協助做者解決!"); } PlainSelect plainSelect = (PlainSelect) setSelectBody; PlainSelect selectBody = new PlainSelect(); List<SelectItem> selectItems = getSelectItems(plainSelect); selectBody.setSelectItems(selectItems); // 設置fromIterm SubSelect fromItem = new SubSelect(); fromItem.setSelectBody(setOperationList); fromItem.setAlias(new Alias(WRAP_TABLE)); selectBody.setFromItem(fromItem); // order by if (isNotEmptyList(plainSelect.getOrderByElements())) { selectBody.setOrderByElements(plainSelect.getOrderByElements()); plainSelect.setOrderByElements(null); } return selectBody; } /** * 獲取查詢列 * * @param plainSelect * @return */ protected List<SelectItem> getSelectItems(PlainSelect plainSelect) { // 設置selectItems List<SelectItem> selectItems = new ArrayList<SelectItem>(); for (SelectItem selectItem : plainSelect.getSelectItems()) { // 別名須要特殊處理 if (selectItem instanceof SelectExpressionItem) { SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem; if (selectExpressionItem.getAlias() != null) { // 直接使用別名 Column column = new Column(selectExpressionItem.getAlias().getName()); SelectExpressionItem expressionItem = new SelectExpressionItem(column); selectItems.add(expressionItem); } else if (selectExpressionItem.getExpression() instanceof Column) { Column column = (Column) selectExpressionItem.getExpression(); SelectExpressionItem item = null; if (column.getTable() != null) { Column newColumn = new Column(column.getColumnName()); item = new SelectExpressionItem(newColumn); selectItems.add(item); } else { selectItems.add(selectItem); } } else { selectItems.add(selectItem); } } else if (selectItem instanceof AllTableColumns) { selectItems.add(new AllColumns()); } else { selectItems.add(selectItem); } } return selectItems; } /** * 最外層的SQL查詢須要增長ROW_NUMBER() * * @param plainSelect */ protected void addRowNumber(PlainSelect plainSelect) { // 增長ROW_NUMBER() StringBuilder orderByBuilder = new StringBuilder(); orderByBuilder.append("ROW_NUMBER() OVER ("); if (isNotEmptyList(plainSelect.getOrderByElements())) { // ByLouis 使用別名,自動找出哪一列,而後進行排序 for (OrderByElement orderByElement : plainSelect.getOrderByElements()) { String orderName = orderByElement.getExpression().toString(); // 若是排序列已經帶.,如A.TypeId,則不用處理 int indexOfPoint = orderName.indexOf("."); if (indexOfPoint >= 0) break; // 找出排序列名 String realFieldName = ""; for (SelectItem selectItem : plainSelect.getSelectItems()) { // 首先找到前臺傳過來的字段所在的列 // selectItem.toString()能夠有4種格式 // 直接select字段:HelpCode // 表加字段:A.HelpCode // 直接select字段加別名:HelpCode as NewCode // 表加字段加別名:A.HelpCode as NewCode // 前臺傳過來的字段:有別名則是別名,列名則是列名 // 查找規則:最後一個空格,或最後一個.後面的數據 String selectName = selectItem.toString(); int lastIndexOfSpace = selectName.lastIndexOf(" "); int lastIndexOfPoint = selectName.lastIndexOf("."); int startGetIndex = 0; if (lastIndexOfSpace > startGetIndex) startGetIndex = lastIndexOfSpace; if (lastIndexOfPoint > startGetIndex) startGetIndex = lastIndexOfPoint; if (startGetIndex == 0) startGetIndex = 1; else startGetIndex++; String fieldName = selectName.substring(startGetIndex); System.out.println(fieldName); if (fieldName.toUpperCase().equals(orderName.toUpperCase())) { realFieldName = selectName; // 找到對應select的字段 // 查找規則 第一個空格前面 int firstIndexOfSpace = selectName.indexOf(" "); if (firstIndexOfSpace >= 0) realFieldName = realFieldName.substring(0, firstIndexOfSpace); break; } } orderByElement.setExpression(new Column(realFieldName)); } orderByBuilder.append(PlainSelect.orderByToString(false, plainSelect.getOrderByElements())); } else { throw new RuntimeException("請您在sql中包含order by語句!"); } // 須要把改orderby清空 if (isNotEmptyList(plainSelect.getOrderByElements())) { plainSelect.setOrderByElements(null); } orderByBuilder.append(") "); orderByBuilder.append(PAGE_ROW_NUMBER); Column orderByColumn = new Column(orderByBuilder.toString()); plainSelect.getSelectItems().add(0, new SelectExpressionItem(orderByColumn)); } /** * 處理selectBody去除Order by * * @param selectBody */ protected void processSelectBody(SelectBody selectBody, int level) { if (selectBody instanceof PlainSelect) { processPlainSelect((PlainSelect) selectBody, level + 1); } else if (selectBody instanceof WithItem) { WithItem withItem = (WithItem) selectBody; if (withItem.getSelectBody() != null) { processSelectBody(withItem.getSelectBody(), level + 1); } } else { SetOperationList operationList = (SetOperationList) selectBody; if (operationList.getSelects() != null && operationList.getSelects().size() > 0) { List<SelectBody> plainSelects = operationList.getSelects(); for (SelectBody plainSelect : plainSelects) { processSelectBody(plainSelect, level + 1); } } } } /** * 處理PlainSelect類型的selectBody * * @param plainSelect */ protected void processPlainSelect(PlainSelect plainSelect, int level) { if (level > 1) { if (isNotEmptyList(plainSelect.getOrderByElements())) { if (plainSelect.getTop() == null) { plainSelect.setTop(TOP100_PERCENT); } } } if (plainSelect.getFromItem() != null) { processFromItem(plainSelect.getFromItem(), level + 1); } if (plainSelect.getJoins() != null && plainSelect.getJoins().size() > 0) { List<Join> joins = plainSelect.getJoins(); for (Join join : joins) { if (join.getRightItem() != null) { processFromItem(join.getRightItem(), level + 1); } } } } /** * 處理子查詢 * * @param fromItem */ protected void processFromItem(FromItem fromItem, int level) { if (fromItem instanceof SubJoin) { SubJoin subJoin = (SubJoin) fromItem; if (subJoin.getJoin() != null) { if (subJoin.getJoin().getRightItem() != null) { processFromItem(subJoin.getJoin().getRightItem(), level + 1); } } if (subJoin.getLeft() != null) { processFromItem(subJoin.getLeft(), level + 1); } } else if (fromItem instanceof SubSelect) { SubSelect subSelect = (SubSelect) fromItem; if (subSelect.getSelectBody() != null) { processSelectBody(subSelect.getSelectBody(), level + 1); } } else if (fromItem instanceof ValuesList) { } else if (fromItem instanceof LateralSubSelect) { LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem; if (lateralSubSelect.getSubSelect() != null) { SubSelect subSelect = lateralSubSelect.getSubSelect(); if (subSelect.getSelectBody() != null) { processSelectBody(subSelect.getSelectBody(), level + 1); } } } // Table時不用處理 } /** * List不空 * * @param list * @return */ public boolean isNotEmptyList(List<?> list) { if (list == null || list.size() == 0) { return false; } return true; } /** * 轉換爲分頁語句 * * @param sql * @return */ public String convertToPageSql(String sql) { return convertToPageSql(sql, null, null); } /** * 轉換爲分頁語句 * * @param sql * @param offset * @param limit * @return */ public String convertToPageSql(String sql, Integer offset, Integer limit) { // 解析SQL Statement stmt; try { stmt = CCJSqlParserUtil.parse(sql); } catch (Throwable e) { throw new PageException("不支持該SQL轉換爲分頁查詢!"); } if (!(stmt instanceof Select)) { throw new PageException("分頁語句必須是Select查詢!"); } // 獲取分頁查詢的select Select pageSelect = getPageSelect((Select) stmt); String pageSql = pageSelect.toString(); // 緩存移到外面了,因此不替換參數 if (offset != null) { pageSql = pageSql.replace(START_ROW, String.valueOf(offset)); } if (limit != null) { pageSql = pageSql.replace(PAGE_SIZE, String.valueOf(limit)); } return pageSql; } }
<table tableName="Customer"> <columnOverride column="RowId" isGeneratedAlways="true"></columnOverride> </table>
代碼生成器原來寫法:
<table tableName="Customer"> <generatedKey column="RowId" sqlStatement="SQLServer" identity="true"/> </table>
生成Mapper中的Insert語句:
<insert id="insert" parameterType="com.LTSolution.ShopApp.Model.Customer"> <selectKey keyProperty="rowid" order="AFTER" resultType="java.lang.Integer"> SELECT SCOPE_IDENTITY() </selectKey> insert into Company (...) values (...) </insert>
按照查看相關書籍和資料,這個寫法應該是可行的,可是實際不行。
通過測試使用下面的寫法成功插入後返回主鍵
代碼生成器寫法:
<table tableName="Customer"> <generatedKey column="RowId" sqlStatement="JDBC" identity="true"/> </table>
生成Mapper中的Insert語句:
<insert id="insert" keyColumn="RowId" keyProperty="rowid" parameterType="com.LTSolution.ShopApp.Model.Customer" useGeneratedKeys="true"> insert into Customer (...) values (...) </insert>
原來使用4.1.7,當2個表關聯查詢的時候若是有同名,Select的時候給予了別名,使用PageHelper.orderBy("別名"),會出現錯誤
由於生成的語句是
SELECT TOP 3 Name,CTypeId FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY TypeId DESC) PAGE_ROW_NUMBER, A.Name,A.TypeId as CTypeId FROM Customer A LEFT JOIN CustomerType B ON A.TypeId = B.TypeId ) AS PAGE_TABLE_ALIAS WHERE PAGE_ROW_NUMBER > 0 ORDER BY PAGE_ROW_NUMBER
由於(ORDER BY TypeId DESC)中TypeId是不知道哪一個表的
後來查看更新記錄,後面的版本更新了這個問題
如今使用最新版本5.1.3,生成的語句是
SELECT TOP 3 Name,CTypeId FROM ( SELECT ROW_NUMBER() OVER (ORDER BY CTypeId DESC) PAGE_ROW_NUMBER, Name, CTypeId FROM ( SELECT A.Name, A.TypeId AS CTypeId FROM Customer A LEFT JOIN CustomerType B ON A.TypeId = B.TypeId ) AS PAGE_TABLE_ALIAS ) AS PAGE_TABLE_ALIAS WHERE PAGE_ROW_NUMBER > 0 ORDER BY PAGE_ROW_NUMBER
可見其中增長了一個子查詢,解決了這個問題
使用select,返回值爲Map,結果一些空的字段,不會寫到Map中,致使前端接收不到該字段的值
只須要在mybatis-config.xml配置文件中加入一下設置代碼便可(注意,要寫到前面,寫到後面XML文件會報錯)
<configuration> <settings> <setting name="callSettersOnNulls" value="true" /> </settings> </configuration>
1.1的例子中使用MyBatis鏈接MySQL數據庫,若是使用SQL Server數據庫,能夠進行如下設置
<dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>sqljdbc4</artifactId> <version>4.0</version> </dependency>
<dataSource type="UNPOOLED"> <property name="driver" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" /> <property name="url" value="jdbc:sqlserver://127.0.0.1;database=mybatis;" /> <property name="username" value="sa" /> <property name="password" value="sa" /> </dataSource>