【Mybatis】源碼分析-高級應用

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、常用注解

  1. @Insert:實現新增
  2. @Update:實現更新
  3. @Delete:實現刪除
  4. @Select:實現查詢
  5. @Result:實現結果集封裝
  6. @Results:可以與@Result ?起使?,封裝多個結果集
  7. @One:實現?對?結果集封裝
  8. @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所允許攔截的?法如下:

  1. 執?器Executor (update、query、commit、rollback等?法);
  2. SQL語法構建器StatementHandler (prepare、parameterize、batch、updates query等?法);
  3. 參數處理器ParameterHandler (getParameterObject、setParameters?法);
  4. 結果集處理器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是將分?的復雜操作進?封裝,使?簡單的?式即可獲得分?的相關數據。

開發步驟:

  1. 導?通?PageHelper的坐標
  2. 在mybatis核?配置?件中配置PageHelper插件
  3. 測試分?數據獲取

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);}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/23162.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/23162.shtml
英文地址,請注明出處:http://en.pswp.cn/web/23162.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

技巧:合并ZIP分卷壓縮包

如果ZIP壓縮文件文件體積過大&#xff0c;大家可能會選擇“分卷壓縮”來壓縮ZIP文件&#xff0c;那么&#xff0c;如何合并zip分卷壓縮包呢&#xff1f;今天我們分享兩個ZIP分卷壓縮包合并的方法給大家。 方法一&#xff1a; 我們可以將分卷壓縮包&#xff0c;通過解壓的方式…

E10:系統彈窗提示

效果– window.WeFormSDK.showMessage("這是一個E10的提示", 3, 2); const onClickCreate () > console.log("create"); const onClickSave () > console.log("save"); const onClickCancel () > dialogComponent?.destroy(); co…

Java四舍五入保留小數

這里介紹兩種方法&#xff1a; package Book.jj.hh;import java.text.DecimalFormat; //使用DecimalFormat類 public class Demo1 {public static void main(String[] args) {double num 123.52631;DecimalFormat a new DecimalFormat("#.00"); //小數點后有幾個0…

SpringCloud Gateway基礎入門與使用實踐總結

官網文檔&#xff1a;點擊查看官網文檔 Cloud全家桶中有個很重要的組件就是網關&#xff0c;在1.x版本中都是采用的Zuul網關。但在2.x版本中&#xff0c;zuul的升級一直跳票&#xff0c;SpringCloud最后自己研發了一個網關替代Zuul&#xff0c;那就是SpringCloud Gateway一句話…

抖音賬號永久封號后強制注銷釋放實名!一分鐘教程方法公開

目前方法是可行的&#xff0c;不知道能保持多久&#xff01; 下載舊版本抖音&#xff1a;下載抖音6.8版本或5.8版本的老版本應用。 使用封禁手機號登錄&#xff1a;使用已被永久封禁的手機號登錄舊版本的抖音應用。 賬號注銷操作&#xff1a; 在設置中找到賬號與安全的選項。…

從零開始發布你的第一個npm插件包并在多項目中使用

引言 在開源的世界里&#xff0c;每個人都有機會成為貢獻者&#xff0c;甚至是創新的引領者。您是否有過這樣的想法&#xff1a;開發一個解決特定問題的小工具&#xff0c;讓她成為其他開發者手中的利器&#xff1f;今天&#xff0c;我們就來一場實戰訓練&#xff0c;學習如何將…

【ubuntu】增加samba服務和文件夾

發現ai -server的ubuntu機器無法git clone 下來github的文件所以 使用samba 連接到linux的文件夾proj然后在我的windows上git clone 即可。安裝samba Creating config file /etc/samba/smb.conf with new version Setting up libcephfs2 (17.2.7-0ubuntu0.22.04.1) ... Setting…

漏洞挖掘 | 驗證碼繞過

還是老規矩&#xff0c;開局一個登錄框&#xff0c;中途漏洞全靠舔&#xff0c;先來研究一下這個登錄窗口 很好&#xff0c;發現有驗證碼登錄&#xff0c;先測試測試能不能并發 看來沒有&#xff0c;只成功發送了兩條&#xff0c;再看看驗證碼是不是4位 很好&#xff0c;是4位。…

UE5-AI

AI角色 角色控制器 AI角色必須要一個角色控制器 角色控制器最基本只需要執行行為樹&#xff0c;在EventOnPossess后runBehaviorTree 如果要的是一個角色&#xff0c;可以創建一個Character&#xff0c;在類默認設置中可以找到 Pawn->AIControllerClass&#xff0c;在這里…

Android 架構組件面試問答

1. 什么是 Android Architecture Components&#xff1f; 答&#xff1a; 組件是一組庫&#xff0c;可幫助您設計健壯、可測試且可維護的應用程序。它們提供了一種清晰且慣用的方式來使用 REST API。這些組件包括 Room、ViewModel、LiveData 等。 2. 什么是LiveData&#xff…

Java接口的變更過程

Java 接口相信所有學過 Java 的人一定都知道&#xff0c;而且 99% 的人一定都背過這個面試題&#xff1a;Java 接口和抽象類的區別是什么&#xff1f;答案都是什么接口方法不能有實現&#xff0c;都是抽象的&#xff0c;接口的方法都默認為 public 修飾等等之類的&#xff0c;這…

推箱子小游戲C++

推箱子是一款經典的益智游戲&#xff0c;玩家需要通過推動箱子來達到特定的目標。在C中實現這樣的小游戲需要考慮游戲邏輯、用戶輸入、圖形界面&#xff08;如果需要的話&#xff09;以及可能的關卡設計。 下面是一個簡單的推箱子游戲的實現框架&#xff1a; 定義游戲環境 創建…

DSP問題:CCS更改工程名導入報錯

1、問題現象 復制一個工程出來后&#xff0c;修改版本號&#xff0c;重新導入工程后報錯。 顯示項目描述無效。 2、問題原因 由于CCS無法通過工程描述中找到指定名字文件夾。使用記事本打開.project文件&#xff0c;里面的描述還是以前的文件夾名&#xff0c;所以導入時報…

Spring Boot 開發 -- 靜態資源配置詳解

一、引言 在開發Web應用程序時&#xff0c;靜態資源的管理和配置是一個重要的環節。Spring Boot框架為開發者提供了便捷的靜態資源配置方式&#xff0c;使得我們可以輕松地管理如HTML、CSS、JavaScript、圖片等靜態資源。本文將詳細介紹如何在Spring Boot項目中配置和管理靜態…

Innodb Buffer Pool緩存機制(三)Innodb Buffer Pool內部組成

一、控制塊緩存頁 Buffer Pool中默認的緩存頁大小和在磁盤上默認的頁大小是一樣的&#xff0c;都是16KB。為了更好的管理這些在Buffer Pool中的緩存頁&#xff0c;InnoDB為每一個緩存頁都創建了一些所謂的控制信息&#xff0c;這些控制信息包括該頁所屬的表空間編號、頁號、緩存…

Android基礎-AndroidManifest.xml詳解

在Android開發中&#xff0c;AndroidManifest.xml 文件是一個至關重要的組成部分&#xff0c;它位于應用的根目錄的 app/src/main/ 文件夾下。這個文件提供了Android系統和其他應用所需的所有關于應用的元數據信息。以下是對 AndroidManifest.xml 文件的詳細解析。 1. 文件結構…

[Vulfocus解題系列]spring 命令執行(CVE-2022-22947)

環境部署 使用docker部署環境 漏洞等級&#xff1a;高危 3 月 1 日&#xff0c;VMware 官方發布安全公告&#xff0c;聲明對 Spring Cloud Gateway 中的一處命令注入漏洞進行了修復&#xff0c;漏洞編號為CVE-2022-22947 Spring官方發布 漏洞描述 使用 Spring Cloud Gate…

javaweb—Vue

重點為&#xff1a;雙向數據綁定。 框架&#xff1a;是一個半成品軟件&#xff0c;是一套可重用的、通用的、軟件基礎代碼模型&#xff0c;基于框架進行開發&#xff0c;更加快捷&#xff0c;更加高效。 Vue快速入門 基礎框架&#xff1a; <!DOCTYPE html> <html lan…

【Python Cookbook】S01E20 fnmatch 模塊做字符串匹配

目錄 問題解決方案討論 問題 在不同的操作系統下&#xff0c;怎樣做字符串匹配&#xff1f; 解決方案 fnmatch() 模塊提供兩個函數&#xff0c;fnmatch() 以及 fnmatchcase() 可以用來執行做這樣的匹配。 from fnmatch import fnmatch, fnmatchcasematch_res fnmatch(foo.…

vue路由緩存

vue路由緩存 在業務場景中有時候需要頁面緩存不清空&#xff0c;那么就需要保留緩存(include為需要緩存&#xff0c;而exclude為不緩存&#xff0c;且優先級大于include) <KeepAlive> 是一個內置組件&#xff0c;它的功能是在多個組件間動態切換時緩存被移除的組件實例…