mybatis升級案例之CRUD操作
一、準備工作
1.新建maven工程,和入門案例一樣
主要步驟如下,可參考mybatis入門實例
a.配置pom.xml文件
b.新建實例類User、DAO接口類IUserDao
c.新建主配置文件SqlMapConfig.xml,映射配置文件IUserDao.xml,log4j配置文件log4j.properties
d.新建測試類MybatisTest,這裏不再採用main函數運行,而是測試函數,代碼如下:
public class MybatisTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
@Before//用於在測試方法執行之前運行
public void init() throws IOException {
//1.讀取配置文件,生成字節輸入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.獲取SqlSessionFactory對象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.獲取SqlSession對象
sqlSession = factory.openSession();
//4.獲取dao的代理對象
userDao = sqlSession.getMapper(IUserDao.class);
}
@After//用於在測試方法執行之後運行
public void close() throws IOException {
//5.提交事務
sqlSession.commit();
//6.釋放資源
sqlSession.close();
in.close();
}
/**
* 測試查詢所有
*/
@Test
public void testFindAll() throws IOException {
//執行查詢所有方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}
}
2.運行結果
3.測試函數分析
由於採用單元測試的方式,所以要在測試函數處加上@Test註解,@Before註解表示在測試方法之前執行,@After註解表示在每一個測試方法之後都執行一次。因此,本案例中方法執行順序爲:@Before -> @Test -> @After。關於JUnit註解,可以參考:[JUnit4中@Before、@After、@Test等註解的作用
二、最基本的增刪改查操作
1.在IUserDao接口中添加如下方法
/* 保存用戶 */
void saveUser(User user);
/* 更新用戶 */
void updateUser(User user);
/* 根據用戶Id刪除用戶 */
void deleteUser(Integer userId);
/* 根據用戶Id查詢用戶 */
User findById(Integer useId);
2.配置映射配置文件IUserDao.xml
<!-- 配置保存用戶 -->
<insert id="saveUser" parameterType="domain.User">
insert into user(username, address,sex, birthday)
values(#{username}, #{address},#{sex},#{birthday})
</insert>
<!-- 配置更新用戶 -->
<update id="updateUser" parameterType="domain.User">
update user set username = #{username}, address = #{address},
sex = #{sex}, birthday = #{birthday} where id = #{id}
</update>
<!-- 配置刪除用戶 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where id = #{id}
</delete>
<!-- 配置根據Id查詢用戶 -->
<select id="findById" parameterType="Integer" resultType="domain.User">
select * from user where id = #{id}
</select>
parameterType屬性表示參數類型,resultType屬性表示返回值類型。基本類型的寫法比較隨意,例如Int、Integer、java.lang.Integer,並且類名不區分大小寫。實體類類型必須是全限定類名,區分大小寫。
sql 語句中使用#{}字符: 它代表佔位符,相當於 jdbc 的prepareStatement中的?,都是用於執行語句時替換實際的數據。具體的數據是由#{}裏面的內容決定的。
#{}中內容的寫法: 如果數據類型是基本類型,此處可以隨意寫,例如根據Id查詢用戶中,內容可以是任意字符。對於保存用戶而言,參數類型是一個User對象,此處要寫User對象中的屬性名稱,這裏用的是ognl表達式。
3.在測試類中添加測試方法
/**
* 測試保存用戶
* @throws IOException
*/
@Test
public void testSaveUser() throws IOException {
User user = new User();
user.setUsername("li yier");
user.setAddress("湖北省武漢市");
user.setSex("男");
user.setBirthday(new Date());
userDao.saveUser(user);
}
/**
* 測試更新用戶
* @throws IOException
*/
@Test
public void testUpdateUser() throws IOException {
User user = new User();
user.setId(50);
user.setUsername("li san");
user.setAddress("湖北省武漢市");
user.setSex("男");
user.setBirthday(new Date());
userDao.updateUser(user);
}
/**
* 測試刪除用戶
* @throws IOException
*/
@Test
public void testDeleteUser() throws IOException {
userDao.deleteUser(49);
}
/**
* 測試根據用戶ID查詢用戶
* @throws IOException
*/
@Test
public void testFindById() throws IOException {
User user = userDao.findById(50);
System.out.println(user);
}
4.爲什麼在close方法中需要提交事務
openSession方法中,除非傳入boolean autoCommit=true,否則默認都是false。也就是說,不會自動提交事務,在執行close方法的時候就會造成事務回滾。詳解請參考mybatis JDBC事務細節
三、模糊查詢和增加用戶獲取id
1.在IUserDao接口中添加如下方法
/* 根據名稱模糊查詢用戶信息 */
List<User> findByName(String username);
/* 查詢總的用戶數 */
int findTotal();
2.配置映射配置文件IUserDao.xml
<!-- 配置保存用戶 -->
<insert id="saveUser" parameterType="domain.User">
<!-- 配置插入用戶後,獲取用戶id -->
<selectKey keyProperty="id" keyColumn="id" resultType="Integer" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username, address,sex, birthday)
values(#{username}, #{address},#{sex},#{birthday})
</insert>
<!-- 配置根據名稱模糊查詢用戶 -->
<select id="findByName" parameterType="String" resultType="domain.User">
<!-- select * from user where username like #{name} -->
select * from user where username like '%${value}%'
</select>
<!-- 配置查詢總的用戶數 -->
<select id="findTotal" resultType="Integer">
select count(id) from user
</select>
3.在測試類中添加測試方法
/**
* 測試保存用戶
* @throws IOException
*/
@Test
public void testSaveUser() throws IOException {
User user = new User();
user.setUsername("li yier");
user.setAddress("湖北省武漢市");
user.setSex("男");
user.setBirthday(new Date());
System.out.println("插入數據庫之前:" + user);
userDao.saveUser(user);
System.out.println("插入數據庫之後:" + user);
}
/**
* 測試根據名稱模糊查詢用戶
* @throws IOException
*/
@Test
public void testFindByName() throws IOException {
//List<User> users = userDao.findByName("%王%");
List<User> users = userDao.findByName("王");
for (User user : users
) {
System.out.println(user);
}
}
/**
* 測試根據查詢用戶記錄條數
* @throws IOException
*/
@Test
public void testFindTotal() throws IOException {
int total = userDao.findTotal();
System.out.println("用戶總數爲:" + total);
}
4.代碼分析
1.添加用戶時,如何獲取用戶id
新增用戶後,同時還要返回當前新增用戶的 id 值,因爲 id 是由數據庫的自動增長來實現的,所以就相當於我們要在新增後將自動增長 auto_increment 的值返回。使用selectKey標籤,keyProperty表示實體類屬性,keyColumn表示數據庫列名,resultType表示返回值類型,order表示執行順序。此時執行測試方法,就會發現插入數據庫之前user.id爲null,插入之後user.id爲數據庫自動增長的id值。
2.模糊查詢的幾種寫法
第一種是類似於佔位符的寫法:select * from user where username like #{name},此時執行測試函數時,傳入的參數必須帶有%(因爲是模糊查詢),即List<User> users = userDao.findByName("%王%")。運行一下,從控制檯可以看到,sql語句爲Preparing: select * from user where username like ?,Parameters: %王%(String)。說明此時是按照預處理語句的佔位符?來執行的。
第二種是字符串拼接的寫法:select * from user where username like '%${value}%', 此時執行測試函數時,傳入的參數不用帶有%,即List<User> users = userDao.findByName("王")。運行一下,從控制檯可以看到,sql語句爲Preparing: select * from user where username like '%王%' , Parameters: 。說明此時是採用字符串拼接,即直接用“王”替換${value}。注意如果用模糊查詢的這種寫法,那麼${value}的寫法就是固定的,不能寫成其它名字。
四、使用實體類封裝查詢條件
在執行查詢時,有時候我們的查詢條件有多個複雜的參數,這時候我們可以把這些參數封裝到一個pojo對象中執行查詢。關於pojo對象,可以參考java的幾種對象(PO,VO,DAO,BO,POJO,DTO)解釋。這裏我們以根據用戶名查詢用戶信息爲例,查詢條件放到 QueryVo 的 user 屬性中。
1.新建pojo類
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
2.配置映射配置文件IUserDao.xml
<!-- 配置根據封裝pojo對象查詢用戶 -->
<select id="findUserByVo" parameterType="domain.QueryVo" resultType="domain.User">
select * from user where username like #{user.username}
</select>
3.在測試類中添加測試方法
/**
* 測試根據封裝pojo對象進行查詢
* @throws IOException
*/
@Test
public void testFindByVo() throws IOException {
QueryVo vo = new QueryVo();
User u = new User();
u.setUsername("%王%");
vo.setUser(u);
List<User> users = userDao.findUserByVo(vo);
for (User user : users
) {
System.out.println(user);
}
}
4.查詢結果與模糊查詢一致
五、實體類屬性和數據庫列名不一致問題
在之前的案例中,總是保持實體類屬性和數據庫列名一致,但是在實際開發中,總會有實體類屬性和數據列名不一致的情況。如果將User類所有屬性之前都加上user,例如id改爲userId。由於在查詢時,我們將查詢結果封裝到實體類時,採用了反射技術,如果不知道準確的實體類屬性名時,就無法封裝到實體類對象中。對於這種情況,我們有兩種解決方式。
1.修改sql語句
可以在sql語句中給列名起別名,例如映射配置文件IUserDao.xml中查詢所有方法的配置可以改爲:
<!-- 配置查詢所有 -->
<select id="findAll" resultType="domain.User">
<!-- 用給列名起別名的方式解決實體類屬性名和數據庫列名不一致的問題,此方法查詢較快 -->
select id as userId, username as userName, address as userAddress, sex as userSex,
birthday as userBirthday from user;
</select>
2.在映射配置文件IUserDao.xml中指定實體類屬性和數據庫列名的映射關係
<!-- 配置數據庫的列名和實體類的屬性名的對應關係 -->
<resultMap id="userMap" type="domain.User">
<!-- 配置主鍵 -->
<id property="userId" column="id"></id>
<!-- 配置非主鍵 -->
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</resultMap>
property表示實體類屬性名,column表示數據庫列名。當指定映射關係之後,查詢所有方法的配置,應該改爲:
<!-- 配置查詢所有 -->
<select id="findAll" resultMap="userMap">
select * from user;
</select>
這裏不再使用resultType屬性,而是resultMap屬性。這種方法較直接更改sql語句而言執行相對較慢,因爲多了一步對xml文件resultMap屬性的解析。
六、重要的標籤使用
在主配置文件SqlMapConfig.xml中,有一些標籤可以簡化我們的代碼。主要有以下:
1.properties標籤
在配置環境時,我們需要配置mysql的連接池信息。原始配置如下:
<!-- 配置環境 -->
<environments default="mysql">
<!-- 配置mysql的環境 -->
<environment id="mysql">
<!-- 配置事務 -->
<transactionManager type="jdbc"></transactionManager>
<!-- 配置連接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</dataSource>
</environment>
</environments>
在SqlMapConfig.xml中配置properties標籤,有兩種方式:
1.通過resource屬性引用外部配置文件信息
我們可以將連接池的相關信息放在一個文件中,然後指定相關信息即可。例如,在resource目錄下,新建名爲jdbcConfig.properties的文件,內容如下:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=12345678
在SqlMapConfig.xml中添加properties標籤如下:
<!-- 配置properties -->
<!-- 方法一:通過resource屬性引用外部配置文件信息,resource屬性用於指定配置文件的位置,
並且必須存在於類路徑下 -->
<properties resource="jdbcConfig.properties">
</properties>
2.在標籤內部配置鏈接數據庫的信息
<properties>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</properties>
配置properties標籤之後,就可以將mysql的連接池信息更改爲:
<!-- 配置環境 -->
<environments default="mysql">
<!-- 配置mysql的環境 -->
<environment id="mysql">
<!-- 配置事務 -->
<transactionManager type="jdbc"></transactionManager>
<!-- 配置連接池 -->
<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>
2.typealias標籤
typealias標籤可以用於給實體類取別名,type屬性指的是實體類全限定類名,alias屬性指定別名,當指定別名後,就不再區分別名的大小寫。當配置別名後,在映射配置文件IUserDao.xml中,參數類型不再需要寫全限定類名,只需要寫別名即可。定義別名有兩種方式:
1.單個別名定義:
<!-- 使用typeAliases配置別名,只能給實體類(domain包下的類)取別名 -->
<typeAliases>
<!-- typeAlias用於配置別名,type屬性指的是實體類全限定類名,alias屬性指定別名
當指定別名後,就不再區分別名的大小寫 -->
<typeAlias type="domain.User" alias="user"></typeAlias>
</typeAliases>
2.批量別名定義:需要用到package標籤
<!-- 使用typeAliases配置別名,只能給實體類(domain包下的類)取別名 -->
<typeAliases>
<!-- package用於指定要配置別名的包,當指定之後,該包下的實體類都會註冊別名,類名就是別名
並且不再區分別名的大小寫 -->
<package name="domain"/>
</typeAliases>
當定義別名後,IUserDao.xml中的全限定類名domain.User可以更改爲user,從而簡化代碼。
3.mappers標籤中的package
在主配置文件SqlMapConfig.xml中,需要通過mappers標籤來指定映射配置文件的位置,有三種方式。
1.如果是通過xml文件的形式來配置,則應該如下配置:
<!-- 配置映射文件的位置,使用相對於類路徑的資源 -->
<mappers>
<mapper resource="dao/IUserDao.xml"></mapper>
</mappers>
2.如果是通過註解的形式來配置,則應該如下配置:
<!-- 配置映射文件的位置,使用 mapper 接口類路徑 -->
<mappers>
<mapper class="dao.IUserDao"></mapper>
</mappers>
3.通過package標籤來配置
<!-- 配置映射文件的位置,註冊指定包下的所有mapper接口 -->
<mappers>
<!-- 此處的package標籤用於指定dao接口所在的包,當指定完成後,就不再需要mapper、resource和class -->
<package name="dao"/>
</mappers>
注意:方法2和方法3要求 mapper 接口名稱和 mapper 映射文件名稱相同,且放在同一個目錄中。