1、Mybatis配置文件深入理解
1.2、動態SQL語句
????????Mybatis 的映射?件中,前?我們的 SQL 都是?較簡單的,有些時候業務邏輯復雜時,我們的 SQL是動態變化的,此時在前?的學習中我們的 SQL 就不能滿?要求了。
1.2.1、條件判斷
????????我們根據實體類的不同取值,使?不同的 SQL語句來進?查詢。?如在 id如果不為空時可以根據id查詢,如果username 不同空時還要加??戶名作為條件。這種情況在我們的多條件組合查詢中經常會碰到。
<select id="findByCondition" parameterType="user" resultType="user">select * from User<where><if test="id!=0">and id=#{id}</if><if test="username!=null">and username=#{username}</if></where>
</select>
????????當查詢條件id和username都存在時,控制臺打印的sql語句如下:
//獲得MyBatis框架?成的UserMapper接?的實現類
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User condition = new User();
condition.setId(1);
condition.setUsername("lucy");
User user = userMapper.findByCondition(condition);
????????當查詢條件只有id存在時,控制臺打印的sql語句如下:
//獲得MyBatis框架?成的UserMapper接?的實現類
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User condition = new User();
condition.setId(1);
User user = userMapper.findByCondition(condition);
1.2.2、循環執行
????????循環執?sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,5)。
<select id="findByIds" parameterType="list" resultType="user">select * from User<where><foreach collection="list" open="id in(" close=")" item="id"
separator=",">#{id}</foreach></where>
</select>
測試代碼?段如下:
//獲得MyBatis框架?成的UserMapper接?的實現類
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int[] ids = new int[]{2,5};
List<User> userList = userMapper.findByIds(ids);
System.out.println(userList);
foreach標簽的屬性含義如下:
標簽?于遍歷集合,它的屬性:
- collection:代表要遍歷的集合元素,注意編寫時不要寫#{}
- open:代表語句的開始部分
- close:代表結束部分
- item:代表遍歷集合的每個元素,?成的變量名
- sperator:代表分隔符
1.2.3、SQL?段抽取
????????Sql 中可將重復的 sql 提取出來,使?時? include 引?即可,最終達到 sql 重?的?的。
<!--抽取sql?段簡化編寫-->
<sql id="selectUser" select * from User</sql><select id="findById" parameterType="int" resultType="user"><include refid="selectUser"></include> where id=#{id}
</select><select id="findByIds" parameterType="list" resultType="user"><include refid="selectUser"></include><where><foreach collection="array" open="id in(" close=")" item="id"
separator=",">#{id}</foreach></where>
</select>
2、Mybatis 復雜映射開發
2.1、一對一查詢
2.1.1、查詢模型
?????????戶表和訂單表的關系為,?個?戶有多個訂單,?個訂單只從屬于?個?戶;?對?查詢的需求:查詢?個訂單,與此同時查詢出該訂單所屬的?戶。
2.1.2、查詢語句
select * from orders o,user u where o.uid=u.id;
2.1.3、實體創建
public class Order {private int id;private Date ordertime;private double total;//代表當前訂單從屬于哪?個客戶private User user;
}public class User {private int id;private String username;private String password;private Date birthday;
}
2.1.4、創建Mapper接口
public interface OrderMapper {List<Order> findAll();
}
2.1.5、創建Mapper.xml
<mapper namespace="com.blnp.net.mapper.OrderMapper"><resultMap id="orderMap" type="com.blnp.net.domain.Order"><result column="uid" property="user.id"></result><result column="username" property="user.username"></result><result column="password" property="user.password"></result><result column="birthday" property="user.birthday"></result></resultMap><select id="findAll" resultMap="orderMap">select * from orders o,user u where o.uid=u.id</select>
</mapper>
其中還可以配置如下:
<resultMap id="orderMap" type="com.blnp.net.domain.Order"><result property="id" column="id"></result><result property="ordertime" column="ordertime"></result><result property="total" column="total"></result><association property="user" javaType="com.blnp.net.domain.User"><result column="uid" property="id"></result><result column="username" property="username"></result><result column="password" property="password"></result><result column="birthday" property="birthday"></result></association>
</resultMap>
2.1.6、測試查詢結果
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List<Order> all = mapper.findAll();
for(Order order : all){System.out.println(order);
}
2.2、一對多查詢
2.2.1、查詢模型
?????????戶表和訂單表的關系為,?個?戶有多個訂單,?個訂單只從屬于?個?戶;?對多查詢的需求:查詢?個?戶,與此同時查詢出該?戶具有的訂單。
2.2.2、查詢語句
select *,o.id oid from user u left join orders o on u.id=o.uid;
2.2.3、修改實體
public class Order {private int id;private Date ordertime;private double total;//代表當前訂單從屬于哪?個客戶private User user;
}public class User {private int id;private String username;private String password;private Date birthday;//代表當前?戶具備哪些訂單private List<Order> orderList;
}
2.2.4、創建Mapper接口
public interface UserMapper {List<User> findAll();
}
2.2.5、配置UserMapper.xml
<mapper namespace="com.blnp.net.mapper.UserMapper"><resultMap id="userMap" type="com.blnp.net.domain.User"><result column="id" property="id"></result><result column="username" property="username"></result><result column="password" property="password"></result><result column="birthday" property="birthday"></result><collection property="orderList" ofType="com.blnp.net.domain.Order"><result column="oid" property="id"></result><result column="ordertime" property="ordertime"></result><result column="total" property="total"></result></collection></resultMap><select id="findAll" resultMap="userMap">select *,o.id oid from user u left join orders o on u.id=o.uid</select>
</mapper>
2.2.6、查詢結果測試
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> all = mapper.findAll();
for(User user : all){System.out.println(user.getUsername());List<Order> orderList = user.getOrderList();for(Order order : orderList){System.out.println(order);}System.out.println("----------------------------------");
}
2.3、多對多查詢
2.3.1、查詢模型
?????????戶表和??表的關系為,?個?戶有多個??,?個??被多個?戶使?;多對多查詢的需求:查詢?戶同時查詢出該?戶的所有??。
2.3.2、查詢語句
select u.,r.,r.id rid from user u left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.id;
2.3.3、實體創建
public class User {private int id;private String username;private String password;private Date birthday;//代表當前?戶具備哪些訂單private List<Order> orderList;//代表當前?戶具備哪些??private List<Role> roleList;
}public class Role {private int id;private String rolename;
}
2.3.4、創建Mapper接口
List<User> findAllUserAndRole();
2.3.5、配置Mapper.xml
<resultMap id="userRoleMap" type="com.blnp.net.domain.User"><result column="id" property="id"></result><result column="username" property="username"></result><result column="password" property="password"></result><result column="birthday" property="birthday"></result><collection property="roleList" ofType="com.blnp.net.domain.Role"><result column="rid" property="id"></result><result column="rolename" property="rolename"></result></collection>
</resultMap><select id="findAllUserAndRole" resultMap="userRoleMap">select u.*,r.*,r.id rid from user u left join user_role ur onu.id=ur.user_idinner join role r on ur.role_id=r.id
</select>
2.3.6、查詢結果測試
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> all = mapper.findAllUserAndRole();
for(User user : all){System.out.println(user.getUsername());List<Role> roleList = user.getRoleList();for(Role role : roleList){System.out.println(role);}System.out.println("----------------------------------");
}
3、Mybatis注解開發
3.1、常用注解
- @Insert:實現新增
- @Update:實現更新
- @Delete:實現刪除
- @Select:實現查詢
- @Result:實現結果集封裝
- @Results:可以與@Result ?起使?,封裝多個結果集
- @One:實現?對?結果集封裝
- @Many:實現?對多結果集封裝
3.2、MyBatis的增刪改查
private UserMapper userMapper;@Before
public void before() throws IOException
{InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = newSqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession(true);userMapper = sqlSession.getMapper(UserMapper.class);
}
@Test
public void testAdd()
{User user = new User();user.setUsername("測試數據");user.setPassword("123");user.setBirthday(new Date());userMapper.add(user);
}
@Test
public void testUpdate() throws IOException
{User user = new User();user.setId(16);user.setUsername("測試數據修改");user.setPassword("abc");user.setBirthday(new Date());userMapper.update(user);
}
@Test
public void testDelete() throws IOException
{userMapper.delete(16);
}
@Test
public void testFindById() throws IOException
{User user = userMapper.findById(1);System.out.println(user);
}
@Test
public void testFindAll() throws IOException
{List < User > all = userMapper.findAll();for(User user: all){System.out.println(user);}
}
????????修改MyBatis的核?配置?件,我們使?了注解替代的映射?件,所以我們只需要加載使?了注解的Mapper接?即可。
<mappers><!--掃描使?注解的類--><mapper class="com.blnp.net.mapper.UserMapper"></mapper>
</mappers>
或者指定掃描包含映射關系的接?所在的包也可以:
<mappers><!--掃描使?注解的類所在的包--><package name="com.blnp.net.mapper"></package>
</mappers>
3.3、復雜映射開發
????????實現復雜關系映射之前我們可以在映射?件中通過配置來實現,使?注解開發后,我們可以使?@Results注解,@Result注解,@One注解,@Many注解組合完成復雜關系的配置。
注解 | 說明 |
---|---|
@Results | 代替的是標簽<resultMap> 該注解中可以使用單個@Result 注解,也可以使用@Result 集合。使用格式: @Results ({@Result(),@Result()}) 或者 @Results(@Result()) |
@Result | 代替了<id> 標簽 和 <result> 標簽: @Result 注解屬性介紹: column:數據庫的列名 property:需要裝配的屬性名 one:需要使用的@One注解(@Result(one=@One)()) many:需要使用的@Many注解(@Result (many=@many)() ) |
@One | 代替了<assocation>標簽,是多查詢的關鍵,在注解中用來指定子查詢返回單一對象。 @One 注解屬性說明: select:指定用來多表查詢的 sqlmapper 使用格式:@Result(column="",property="",one=@One(select="")) |
@Many | 代替了<collection>標簽,是多表查詢的關鍵,在注解中用來指定子查詢返回對象的集合。 使用格式:@Result(property="",column="",many=@Many(select="")) |
3.4、一對一查詢(注解)
3.4.1、查詢模型
3.4.2、查詢語句
select * from orders;select * from user where id=查詢出訂單的uid;
3.4.3、創建實體
public class Order {private int id;private Date ordertime;private double total;//代表當前訂單從屬于哪?個客戶private User user;
}public class User {private int id;private String username;private String password;private Date birthday;
}
3.4.4、創建mapper接口
public interface OrderMapper {List<Order> findAll();
}
3.4.5、使?注解配置Mapper
public interface OrderMapper {@Select("select * from orders")@Results({@Result(id=true,property = "id",column = "id"),@Result(property = "ordertime",column = "ordertime"),@Result(property = "total",column = "total"),@Result(property = "user",column = "uid",javaType = User.class,one = @One(select ="com.blnp.net.mapper.UserMapper.findById"))})List<Order> findAll();
}
public interface UserMapper {@Select("select * from user where id=#{id}")User findById(int id);
}
3.4.6、查詢結果
@Test
public void testSelectOrderAndUser() {List<Order> all = orderMapper.findAll();for(Order order : all){System.out.println(order);}
}
3.5、一對多查詢(注解)
3.5.1、查詢模型
3.5.2、查詢語句
select * from user;select * from orders where uid=查詢出?戶的id;
3.5.3、創建實體
public class Order {private int id;private Date ordertime;private double total;//代表當前訂單從屬于哪?個客戶private User user;
}public class User {private int id;private String username;private String password;private Date birthday;//代表當前?戶具備哪些訂單private List<Order> orderList;
}
3.5.4、創建Mapper接口
List<User> findAllUserAndOrder();
3.5.5、使?注解配置Mapper
public interface UserMapper {@Select("select * from user")@Results({@Result(id = true,property = "id",column = "id"),@Result(property = "username",column = "username"),@Result(property = "password",column = "password"),@Result(property = "birthday",column = "birthday"),@Result(property = "orderList",column = "id",javaType = List.class,many = @Many(select =
"com.blnp.net.mapper.OrderMapper.findByUid"))
})List<User> findAllUserAndOrder();
}public interface OrderMapper {@Select("select * from orders where uid=#{uid}")List<Order> findByUid(int uid);
}
3.5.6、查詢結果
List<User> all = userMapper.findAllUserAndOrder();
for(User user : all){System.out.println(user.getUsername());List<Order> orderList = user.getOrderList();for(Order order : orderList){System.out.println(order);}System.out.println("-----------------------------");
}
3.6、多對多查詢(注解)
3.6.1、查詢模型
3.6.2、查詢語句
select * from user;select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=?戶的id;
3.6.3、創建實體
public class User {private int id;private String username;private String password;private Date birthday;//代表當前?戶具備哪些訂單private List<Order> orderList;//代表當前?戶具備哪些??private List<Role> roleList;
}public class Role {private int id;private String rolename;
}
3.6.4、添加UserMapper接??法
List<User> findAllUserAndRole();
3.6.5、使?注解配置Mapper
public interface UserMapper {@Select("select * from user")@Results({@Result(id = true,property = "id",column = "id"),@Result(property = "username",column = "username"),@Result(property = "password",column = "password"),@Result(property = "birthday",column = "birthday"),@Result(property = "roleList",column = "id",javaType = List.class,many = @Many(select =
"com.blmnp.net.mapper.RoleMapper.findByUid"))
})List<User> findAllUserAndRole();
}public interface RoleMapper {@Select("select * from role r,user_role ur where r.id=ur.role_id andur.user_id=#{uid}")List<Role> findByUid(int uid);
}
3.6.6、查詢結果
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> all = mapper.findAllUserAndRole();
for(User user : all){System.out.println(user.getUsername());List<Role> roleList = user.getRoleList();for(Role role : roleList){System.out.println(role);}System.out.println("----------------------------------");
}
4、Mybatis緩存
4.1、一級緩存
4.1.1、場景一
????????在?個sqlSession中,對User表根據id進?兩次查詢,查看他們發出sql語句的情況。
@Test
public void test1(){//根據 sqlSessionFactory 產? sessionSqlSession sqlSession = sessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//第?次查詢,發出sql語句,并將查詢出來的結果放進緩存中User u1 = userMapper.selectUserByUserId(1);System.out.println(u1);//第?次查詢,由于是同?個sqlSession,會在緩存中查詢結果//如果有,則直接從緩存中取出來,不和數據庫進?交互User u2 = userMapper.selectUserByUserId(1);System.out.println(u2);sqlSession.close();
}
查看控制臺打印情況:
4.1.2、場景二
????????同樣是對user表進?兩次查詢,只不過兩次查詢之間進?了?次update操作。
@Test
public void test2(){//根據 sqlSessionFactory 產? sessionSqlSession sqlSession = sessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//第?次查詢,發出sql語句,并將查詢的結果放?緩存中User u1 = userMapper.selectUserByUserId( 1 );System.out.println(u1);//第?步進?了?次更新操作,sqlSession.commit()u1.setSex("?");userMapper.updateUserByUserId(u1);sqlSession.commit();//第?次查詢,由于是同?個sqlSession.commit(),會清空緩存信息//則此次查詢也會發出sql語句User u2 = userMapper.selectUserByUserId(1);System.out.println(u2);sqlSession.close();
}
查看控制臺打印情況:
4.1.3、小結
????????1、第?次發起查詢?戶id為1的?戶信息,先去找緩存中是否有id為1的?戶信息,如果沒有,則從數據庫查詢?戶信息。得到?戶信息,將?戶信息存儲到?級緩存中。
????????2、 如果中間sqlSession去執?commit操作(執?插?、更新、刪除),則會清空SqlSession中的 ?級緩存,這樣做的?的為了讓緩存中存儲的是最新的信息,避免臟讀。
????????3、 第?次發起查詢?戶id為1的?戶信息,先去找緩存中是否有id為1的?戶信息,緩存中有,直接從緩存中獲取?戶信息
4.1.4、原理分析
????????上?我們?直提到?級緩存,那么提到?級緩存就繞不開SqlSession,所以索性我們就直接從SqlSession,看看有沒有創建緩存或者與緩存有關的屬性或者?法。
????????調研了?圈,發現上述所有?法中,好像只有clearCache()和緩存沾點關系,那么就直接從這個方法入?吧,分析源碼時,我們要看它(此類)是誰,它的?類和?類分別?是誰,對如上關系了解了,你才會對這個類有更深的認識,分析了?圈,你可能會得到如下這個流程圖。
????????再深?分析,流程?到Perpetualcache中的clear()?法之后,會調?其cache.clear()?法,那 么這個cache是什么東?呢?點進去發現,cache其實就是private Map cache = new?HashMap();也就是?個Map,所以說cache.clear()其實就是map.clear(),也就是說,緩存其實就是本地存放的?個map對象,每?個SqISession都會存放?個map對象的引?,那么這個cache是何時創建的呢?
????????你覺得最有可能創建緩存的地?是哪?呢?我覺得是Executor,為什么這么認為?因為Executor是執?器,?來執?SQL請求,?且清除緩存的?法也在Executor中執?,所以很可能緩存的創建也很有可能在Executor中,看了?圈發現Executor中有?個createCacheKey?法,這個?法很像是創建緩存的?法啊,跟進去看看,你發現createCacheKey?法是由BaseExecutor執?的,代碼如下:
CacheKey cacheKey = new CacheKey();
//MappedStatement 的 id
// id就是Sql語句的所在位置包名+類名+ SQL名稱
cacheKey.update(ms.getId());
// offset 就是 0
cacheKey.update(rowBounds.getOffset());
// limit 就是 Integer.MAXVALUE
cacheKey.update(rowBounds.getLimit());
//具體的SQL語句
cacheKey.update(boundSql.getSql());
//后?是update 了 sql中帶的參數
cacheKey.update(value);
...
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
????????創建緩存key會經過?系列的update?法,udate?法由?個CacheKey這個對象來執?的,這個update?法最終由updateList的list來把五個值存進去,對照上?的代碼和下?的圖示,你應該能 理解這五個值都是什么了:
????????這?需要注意?下最后?個值,configuration.getEnvironment().getId()這是什么,這其實就是 定義在mybatis-config.xml中的標簽,?如下。
<environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment>
</environments>
????????那么我們回歸正題,那么創建完緩存之后該?在何處呢?總不會憑空創建?個緩存不使?吧?絕對不會的,經過我們對?級緩存的探究之后,我們發現?級緩存更多是?于查詢操作,畢竟?級緩存也叫做查詢緩存吧,為什么叫查詢緩存我們?會?說。我們先來看?下這個緩存到底?在哪了,我們跟蹤到query?法如下:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameter);//創建緩存CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}@SuppressWarnings("unchecked")
Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {...list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {//這個主要是處理存儲過程?的。handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key,boundSql);}...
}// queryFromDatabase ?法
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql
boundSql) throws SQLException {List<E> list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;
}
????????如果查不到的話,就從數據庫查,在queryFromDatabase中,會對localcache進?寫?。 localcache對象的put?法最終交給Map進?存放.
private Map<Object, Object> cache = new HashMap<Object, Object>();@Overridepublic void putObject(Object key, Object value) { cache.put(key, value);
}
4.2、二級緩存
?????????級緩存的原理和?級緩存原理?樣,第?次查詢,會將數據放?緩存中,然后第?次查詢則會直接去緩存中取。但是?級緩存是基于sqlSession的,??級緩存是基于mapper?件的namespace的,也就是說多個sqlSession可以共享?個mapper中的?級緩存區域,并且如果兩個mapper的namespace 相同,即使是兩個mapper,那么這兩個mapper中執?sql查詢到的數據也將存在相同的?級緩存區域中。
4.2.1、開啟二級緩存
????????和?級緩存默認開啟不?樣,?級緩存需要我們?動開啟。?先在全局配置?件sqlMapConfig.xml?件中加?如下代碼:
<!--開啟?級緩存-->
<settings><setting name="cacheEnabled" value="true"/>
</settings>
其次在UserMapper.xml?件中開啟緩存:
<!--開啟?級緩存-->
<cache></cache>
????????我們可以看到mapper.xml?件中就這么?個空標簽,其實這?可以配置,PerpetualCache這個類是mybatis默認實現緩存功能的類。我們不寫type就使?mybatis默認的緩存,也可以去實現Cache接口來?定義緩存。
public class PerpetualCache implements Cache {private final String id;private Map<Object, Object> cache = new HashMap();public PerpetualCache(String id) { this.id = id;
}
我們可以看到?級緩存底層還是HashMap結構:
public class User implements Serializable(//?戶IDprivate int id;//?戶姓名private String username;//?戶性別private String sex;
}
????????開啟了?級緩存后,還需要將要緩存的pojo實現Serializable接?,為了將緩存數據取出執?反序列化操作,因為?級緩存數據存儲介質多種多樣,不?定只存在內存中,有可能存在硬盤中,如果我們要再取這個緩存的話,就需要反序列化了。所以mybatis中的pojo都去實現Serializable接?.
4.2.2、測試驗證
1、測試?級緩存和sqlSession?關
@Test
public void testTwoCache(){//根據 sqlSessionFactory 產? sessionSqlSession sqlSession1 = sessionFactory.openSession();SqlSession sqlSession2 = sessionFactory.openSession();UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class );UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class );//第?次查詢,發出sql語句,并將查詢的結果放?緩存中User u1 = userMapper1.selectUserByUserId(1);System.out.println(u1);sqlSession1.close(); //第?次查詢完后關閉 sqlSession//第?次查詢,即使sqlSession1已經關閉了,這次查詢依然不發出sql語句User u2 = userMapper2.selectUserByUserId(1);System.out.println(u2);sqlSession2.close();
}
????????可以看出上?兩個不同的sqlSession,第?個關閉了,第?次查詢依然不發出sql查詢語句。
2、測試執?commit()操作,?級緩存數據清空
@Test
public void testTwoCache(){//根據 sqlSessionFactory 產? sessionSqlSession sqlSession1 = sessionFactory.openSession();SqlSession sqlSession2 = sessionFactory.openSession();SqlSession sqlSession3 = sessionFactory.openSession();String statement = "com.blnp.net.pojo.UserMapper.selectUserByUserld" ;UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class );UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class );UserMapper userMapper3 = sqlSession2.getMapper(UserMapper. class );//第?次查詢,發出sql語句,并將查詢的結果放?緩存中User u1 = userMapperl.selectUserByUserId( 1 );System.out.println(u1);sqlSessionl .close(); //第?次查詢完后關閉sqlSession//執?更新操作,commit()u1.setUsername( "aaa" );userMapper3.updateUserByUserId(u1);sqlSession3.commit();//第?次查詢,由于上次更新操作,緩存數據已經清空(防?數據臟讀),這?必須再次發出sql語User u2 = userMapper2.selectUserByUserId( 1 );System.out.println(u2);sqlSession2.close();
}
4.2.3、useCache和flushCache
????????mybatis中還可以配置userCache和flushCache等配置項,userCache是?來設置是否禁??級緩存的,在statement中設置useCache=false可以禁?當前select語句的?級緩存,即每次查詢都會發出 sql去查詢,默認情況是true,即該sql使??級緩存。
<select id="selectUserByUserId" useCache="false"resultType="com.blnp.net.pojo.User" parameterType="int">select * from user where id=#{id}
</select>
????????這種情況是針對每次查詢都需要最新的數據sql,要設置成useCache=false,禁??級緩存,直接從數據庫中獲取。
????????在mapper的同?個namespace中,如果有其它insert、update, delete操作數據后需要刷新緩 存,如果不執?刷新緩存會出現臟讀。
????????設置statement配置中的flushCache="true”屬性,默認情況下為true,即刷新緩存,如果改成false則不會刷新。使?緩存時如果?動修改數據庫表中的查詢數據會出現臟讀。
<select id="selectUserByUserId" flushCache="true" useCache="false"resultType="com.blnp.net.pojo.User" parameterType="int">select * from user where id=#{id}
</select>
?????????般下執?完commit操作都需要刷新緩存,flushCache=true表示刷新緩存,這樣可以避免數據庫臟讀。所以我們不?設置,默認即可.
4.3、?級緩存整合redis
????????上?我們介紹了 mybatis?帶的?級緩存,但是這個緩存是單服務器?作,?法實現分布式緩存。 那么什么是分布式緩存呢?假設現在有兩個服務器1和2,?戶訪問的時候訪問了1服務器,查詢后的緩存就會放在1服務器上,假設現在有個?戶訪問的是2服務器,那么他在2服務器上就?法獲取剛剛那個緩存,如下圖所示:
????????為了解決這個問題,就得找?個分布式的緩存,專??來存儲緩存數據的,這樣不同的服務器要緩存數據都往它那?存,取緩存數據也從它那?取,如下圖所示:
????????如上圖所示,在?個不同的服務器之間,我們使?第三?緩存框架,將緩存都放在這個第三?框架中,然后?論有多少臺服務器,我們都能從緩存中獲取數據。
????????剛剛提到過,mybatis提供了?個eache接?,如果要實現??的緩存邏輯,實現cache接?開發即可。mybatis本身默認實現了?個,但是這個緩存的實現?法實現分布式緩存,所以我們要??來實現。redis分布式緩存就可以,mybatis提供了?個針對cache接?的redis實現類,該類存在mybatis-redis包中。
4.3.1、pom坐標
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0-beta2</version>
</dependency>
4.3.2、配置文件
Mapper.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="com.blnp.net.mapper.IUserMapper">
<cache type="org.mybatis.caches.redis.RedisCache" /><select id="findAll" resultType="com.blnp.net.pojo.User" useCache="true">select * from user
</select>
4.3.3、Redis配置
redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0
4.3.4、驗證測試
@Test
public void SecondLevelCache(){SqlSession sqlSession1 = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();SqlSession sqlSession3 = sqlSessionFactory.openSession();IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);IUserMapper mapper2 = sqlSession2.getMapper(lUserMapper.class);IUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);User user1 = mapper1.findUserById(1);sqlSession1.close(); //清空?級緩存User user = new User();user.setId(1);user.setUsername("lisi");mapper3.updateUser(user);sqlSession3.commit();User user2 = mapper2.findUserById(1);System.out.println(user1==user2);
}
4.3.5、源碼分析
????????RedisCache和?家普遍實現Mybatis的緩存?案?同?異,??是實現Cache接?,并使?jedis操作緩存;不過該項?在設計細節上有?些區別;
public final class RedisCache implements Cache {public RedisCache(final String id) {if (id == null) {throw new IllegalArgumentException("Cache instances require anID");}this.id = id;RedisConfig redisConfig =RedisConfigurationBuilder.getInstance().parseConfiguration();pool = new JedisPool(redisConfig, redisConfig.getHost(),redisConfig.getPort(),redisConfig.getConnectionTimeout(),redisConfig.getSoTimeout(), redisConfig.getPassword(),redisConfig.getDatabase(), redisConfig.getClientName());}
}
????????RedisCache在mybatis啟動的時候,由MyBatis的CacheBuilder創建,創建的?式很簡單,就是調?RedisCache的帶有String參數的構造?法,即RedisCache(String id);?在RedisCache的構造?法中,調?了 RedisConfigu rationBuilder 來創建 RedisConfig 對象,并使? RedisConfig 來創建JedisPool。RedisConfig類繼承了 JedisPoolConfig,并提供了 host,port等屬性的包裝,簡單看?下RedisConfig的屬性:
public class RedisConfig extends JedisPoolConfig {private String host = Protocol.DEFAULT_HOST;private int port = Protocol.DEFAULT_PORT;private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;private int soTimeout = Protocol.DEFAULT_TIMEOUT;private String password;private int database = Protocol.DEFAULT_DATABASE;private String clientName;
}
????????RedisConfig對象是由RedisConfigurationBuilder創建的,簡單看下這個類的主要?法:
public RedisConfig parseConfiguration(ClassLoader classLoader) {Properties config = new Properties();InputStream input = classLoader.getResourceAsStream(redisPropertiesFilename);if (input != null) {try {config.load(input);} catch (IOException e) {throw new RuntimeException("An error occurred while reading classpath property '"+ redisPropertiesFilename+ "', see nested exceptions", e);} finally {try {input.close();} catch (IOException e) {// close quietly}}}RedisConfig jedisConfig = new RedisConfig();setConfigProperties(config, jedisConfig);return jedisConfig;
}
????????核?的?法就是parseConfiguration?法,該?法從classpath中讀取?個redis.properties?件,并將該配置?件中的內容設置到RedisConfig對象中,并返回;接下來,就是RedisCache使?RedisConfig類創建完成redisPool;在RedisCache中實現了?個簡單的模板?法,?來操作Redis:
private Object execute(RedisCallback callback) {Jedis jedis = pool.getResource();try {return callback.doWithRedis(jedis);} finally {jedis.close();}
}
????????模板接?為RedisCallback,這個接?中就只需要實現了?個doWithRedis?法?已:
public interface RedisCallback {Object doWithRedis(Jedis jedis);
}
????????接下來看看Cache中最重要的兩個?法:putObject和getObject,通過這兩個?法來查看mybatis-redis儲存數據的格式:
@Override
public void putObject(final Object key, final Object value)
{execute(new RedisCallback(){@Overridepublic Object doWithRedis(Jedis jedis){jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));return null;}});
}
@Override
public Object getObject(final Object key)
{return execute(new RedisCallback(){@Overridepublic Object doWithRedis(Jedis jedis){return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes()));}});
}
????????可以很清楚的看到,mybatis-redis在存儲數據的時候,是使?的hash結構,把cache的id作為這個hash的key (cache的id在mybatis中就是mapper的namespace);這個mapper中的查詢緩存數據作為 hash的field,需要緩存的內容直接使?SerializeUtil存儲,SerializeUtil和其他的序列化類差不多,負責對象的序列化和反序列化;
5、Mybatis插件
5.1、插件簡介
?????????般情況下,開源框架都會提供插件或其他形式的拓展點,供開發者??拓展。這樣的好處是顯?易?的,?是增加了框架的靈活性。?是開發者可以結合實際需求,對框架進?拓展,使其能夠更好的?作。以MyBatis為例,我們可基于MyBatis插件機制實現分?、分表,監控等功能。由于插件和業務?關,業務也?法感知插件的存在。因此可以?感植?插件,在?形中增強功能。
5.2、Mybatis插件介紹
????????Mybatis作為?個應??泛的優秀的ORM開源框架,這個框架具有強?的靈活性,在四?組件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)處提供了簡單易?的插件擴展機制。Mybatis對持久層的操作就是借助于四?核?對象。MyBatis?持?插件對四?核?對象進?攔截,對mybatis來說插件就是攔截器,?來增強核?對象的功能,增強功能本質上是借助于底層的動態代理實現的,換句話說,MyBatis中的四?對象都是代理對象。
MyBatis所允許攔截的?法如下:
- 執?器Executor (update、query、commit、rollback等?法);
- SQL語法構建器StatementHandler (prepare、parameterize、batch、updates query等?法);
- 參數處理器ParameterHandler (getParameterObject、setParameters?法);
- 結果集處理器ResultSetHandler (handleResultSets、handleOutputParameters等?法);
5.3、插件原理
????????在四?對象創建的時候:
- 1、每個創建出來的對象不是直接返回的,?是interceptorChain.pluginAll(parameterHandler);
- 2、獲取到所有的Interceptor (攔截器)(插件需要實現的接?);調? interceptor.plugin(target);返回 target 包裝后的對象
- 3、插件機制,我們可以使?插件為?標對象創建?個代理對象;AOP (?向切?)我們的插件可以為四?對象創建出代理對象,代理對象就可以攔截到四?對象的每?個執?;
插件具體是如何攔截并附加額外的功能的呢?以ParameterHandler來說:
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object object, BoundSql sql, InterceptorChain interceptorChain)
{ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, object, sql);parameterHandler = (ParameterHandler)interceptorChain.pluginAll(parameterHandler);return parameterHandler;
}
public Object pluginAll(Object target)
{for(Interceptor interceptor: interceptors){target = interceptor.plugin(target);}return target;
}
????????interceptorChain保存了所有的攔截器(interceptors),是mybatis初始化的時候創建的。調?攔截器鏈中的攔截器依次的對?標進?攔截或增強。interceptor.plugin(target)中的target就可以理解為mybatis中的四?對象。返回的target是被重重代理后的對象。如果我們想要攔截Executor的query?法,那么可以這樣定義插件:
@Intercepts(
{@Signature(type = Executor.class, method = "query", args = {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})
})
public class ExamplePlugin implements Interceptor
{//省略邏輯
}
????????除此之外,我們還需將插件配置到sqlMapConfig.xml中。
<plugins><plugin interceptor="com.blnp.net.plugin.ExamplePlugin"></plugin>
</plugins>
????????這樣MyBatis在啟動時可以加載插件,并保存插件實例到相關對象(InterceptorChain,攔截器鏈) 中。待準備?作做完后,MyBatis處于就緒狀態。我們在執?SQL時,需要先通過DefaultSqlSessionFactory創建 SqlSession。Executor 實例會在創建 SqlSession 的過程中被創建, Executor實例創建完畢后,MyBatis會通過JDK動態代理為實例?成代理類。這樣,插件邏輯即可在 Executor相關?法被調?前執?。以上就是MyBatis插件機制的基本原理。
5.4、自定義插件
5.4.1、自定義插件接口
Mybatis 插件接?-Interceptor
- Intercept?法,插件的核??法
- plugin?法,?成target的代理對象
- setProperties?法,傳遞插件所需參數
5.4.2、自定義插件
設計實現?個?定義插件
Intercepts(
{ //注意看這個?花括號,也就這說這?可以定義多個@Signature對多個地?攔截,都?這個攔截器@Signature(type = StatementHandler.class, //這是指攔截哪個接?method = "prepare", //這個接?內的哪個?法名,不要拼錯了args = {Connection.class,Integer.class}), // 這是攔截的?法的?參,按順序寫到這, 不要多也不要少, 如果? 法重載, 可是要通過? 法名和? 參來確定唯? 的
})
public class MyPlugin implements Interceptor
{private final Logger logger = LoggerFactory.getLogger(this.getClass());// //這?是每次執?操作的時候,都會進?這個攔截器的?法內@Overridepublic Object intercept(Invocation invocation) throws Throwable{//增強邏輯System.out.println("對?法進?了增強....");return invocation.proceed(); //執?原?法}/*** //主要是為了把這個攔截器?成?個代理放到攔截器鏈中* ^Description包裝?標對象 為?標對象創建代理對象* @Param target為要攔截的對象* @Return代理對象*/@Overridepublic Object plugin(Object target){System.out.println("將要包裝的?標對象:" + target);return Plugin.wrap(target, this);}/**獲取配置?件的屬性**///插件初始化的時候調?,也只調??次,插件配置的屬性從這?設置進來@Overridepublic void setProperties(Properties properties){System.out.println("插件配置的初始化參數:" + properties);}
}
sqlMapConfig.xml
<plugins><plugin interceptor="com.blnp.net.plugin.MySqlPagingPlugin"><!--配置參數--><property name="name" value="Bob"/></plugin>
</plugins>
mapper接?
public interface UserMapper {List<User> selectUser();
}
mapper.xml
<mapper namespace="com.blnp.net.mapper.UserMapper"><select id="selectUser" resultType="com.blnp.net.pojo.User">SELECTid,usernameFROMuser</select>
</mapper>
測試方法
public class PluginTest
{@Testpublic void test() throws IOException{InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = newSqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);List < User > byPaging = userMapper.selectUser();for(User user: byPaging){System.out.println(user);}}
}
5.5、源碼分析
????????Plugin實現了 InvocationHandler接?,因此它的invoke?法會攔截所有的?法調?。invoke?法會對所攔截的?法進?檢測,以決定是否執?插件邏輯。該?法的邏輯如下:
// -Plugin
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable
{try{/**獲取被攔截?法列表,?如:* signatureMap.get(Executor.class), 可能返回 [query, update,commit]*/Set < Method > methods = signatureMap.get(method.getDeclaringClass());//檢測?法列表是否包含被攔截的?法if(methods != null && methods.contains(method)){//執?插件邏輯return interceptor.intercept(new Invocation(target, method, args));//執?被攔截的?法return method.invoke(target, args);}catch (Exception e){}}
????????invoke?法的代碼?較少,邏輯不難理解。?先,invoke?法會檢測被攔截?法是否配置在插件的@Signature注解中,若是,則執?插件邏輯,否則執?被攔截?法。插件邏輯封裝在intercept中,該?法的參數類型為Invocationo Invocation主要?于存儲?標類,?法以及?法參數列表。下?簡單看?下該類的定義:
public class Invocation
{private final Object target;private final Method method;private final Object[] args;public Invocation(Object targetf Method method, Object[] args){this.target = target;this.method = method;//省略部分代碼public Object proceed() throws InvocationTargetException,IllegalAccessException{ //調?被攔截的?法>> —>> ……
5.6、pageHelper分?插件
????????MyBati s可以使?第三?的插件來對功能進?擴展,分?助?PageHelper是將分?的復雜操作進?封裝,使?簡單的?式即可獲得分?的相關數據。
開發步驟:
- 導?通?PageHelper的坐標
- 在mybatis核?配置?件中配置PageHelper插件
- 測試分?數據獲取
5.6.1、導?通?PageHelper坐標
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>3.7.5</version>
</dependency><dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>0.9.1</version>
</dependency>
5.6.2、配置PageHelper插件
<!--注意:分?助?的插件 配置在通?館mapper之前*-->*
<plugin interceptor="com.github.pagehelper.PageHelper"><!—指定?? —><property name="dialect" value="mysql"/>
</plugin>
5.6.3、測試分頁
@Test
public void testPageHelper()
{//設置分?參數PageHelper.startPage(1, 2);List < User > select = userMapper2.select(null);for(User user: select){System.out.println(user);}//其他分?的數據PageInfo < User > pageInfo = new PageInfo < User > (select);System.out.println("總條數:" + pageInfo.getTotal());System.out.println("總?數:" + pageInfo.getPages());System.out.println("當前?:" + pageInfo.getPageNum());System.out.println("每?顯萬?度:" + pageInfo.getPageSize());System.out.println("是否第??:" + pageInfo.isIsFirstPage());System.out.println("是否最后??:" + pageInfo.isIsLastPage());
}
}
5.7、通用Mapper
5.7.1、什么是通?Mapper
????????通?Mapper就是為了解決單表增刪改查,基于Mybatis的插件機制。開發?員不需要編寫SQL,不需要在DAO中增加?法,只要寫好實體類,就能?持相應的增刪改查?法。
5.7.2、導入坐標
<dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId><version>3.1.2</version>
</dependency>
5.7.3、基本配置
<plugins><!--分?插件:如果有分?插件,要排在通?mapper之前--><plugin interceptor="com.github.pagehelper.PageHelper"><property name="dialect" value="mysql"/></plugin><plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor"><!-- 通?Mapper接?,多個通?接??逗號隔開 --><property name="mappers" value="tk.mybatis.mapper.common.Mapper"/></plugin>
</plugins>
5.7.4、實體主鍵配置
@Table(name = "t_user")
public class User
{@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;private String username;
}
5.7.5、定義通用Mapper
import tk.mybatis.mapper.common.Mapper;
public interface UserMapper extends Mapper < User >
{}
5.7.6、驗證測試
public class UserTest
{@Testpublic void test1() throws IOException{Inputstream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory build = newSqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = build.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User user = new User();user.setId(4);//(1)mapper基礎接?//select 接?User user1 = userMapper.selectOne(user); //根據實體中的屬性進?查詢,只能有—個返回值List < User > users = userMapper.select(null); //查詢全部結果userMapper.selectByPrimaryKey(1); //根據主鍵字段進?查詢,?法參數必須包含完整的主鍵屬性, 查詢條件使? 等號userMapper.selectCount(user); //根據實體中的屬性查詢總數,查詢條件使?等號// insert 接?int insert = userMapper.insert(user); //保存?個實體,null值也會保存,不會使?數據庫默認值int i = userMapper.insertSelective(user); //保存實體,null的屬性不會保存,會使? 數據庫默認值// update 接?int i1 = userMapper.updateByPrimaryKey(user); //根據主鍵更新實體全部字段,null值會被更新// delete 接?int delete = userMapper.delete(user); //根據實體屬性作為條件進?刪除,查詢條件 使? 等號userMapper.deleteByPrimaryKey(1); //根據主鍵字段進?刪除,?法參數必須包含完整的主鍵屬性//(2)example?法Example example = new Example(User.class);example.createCriteria().andEqualTo("id", 1);example.createCriteria().andLike("val", "1");//?定義查詢List < User > users1 = userMapper.selectByExample(example);}
}