JNDI
JNDI介绍
JNDI:Java Naming and Directory Interface。是SUN公司推出的一套规范,属于JavaEE技术之一。目的是模仿windows系统中的注册表。在服务器中注册数据源:
JNDI搭建
1.创建maven工程,并且选择webapp选项,勾选
在创建好的项目中,在main文件夹中创建两个文件夹java和resoures,并且把两个文件夹设置为source root 和resources root 源码文件夹,在test目录创建一个java文件夹,设置为 source root文件夹
2.在webapp目下创建目录META-INF,拷贝资料中的context.xml文件到这个文件夹
3.替换主配置文件,拷贝资料中的SqlMapConfig.xml文件的内容替换现有的内容
4.部署并启动tomcatJDNI数据源,只能基于tomcat启动后才能访问,修改index.jsp页面如下
相当于将测试中的创建代理对象的过程搬到了jsp页面
Mybatis延迟加载策略
实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。
何为延迟加载?
延迟加载
: 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.好处
:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。坏处
: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
实现需求
查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可满足要求,当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载。
使用assocation实现延迟加载
账户的持久层DAO接口
//查询所有账户,同时还要获取到当前账户的所属用户信息
List<Account> findAll();
账户的持久层映射文件
<?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.wubo.dao.IAccountDao"><!--表示映射的地址-->
<!--定义封装account和user的resultMap-->
<resultMap id="accountUserMap" type="account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--从表一对一的关系映射,配置封装user的内容
select属性用于指定查询account列表的sql语句,所以填写的是该sql映射的id
column属性指定的内容:用户根据id查询时,所需要的参数的值-->
<association property="user" column="uid" javaType="user" select="com.wubo.dao.IUserDao.findById"></association>
</resultMap>
<!--查询所有操作-->
<select id="findAll" resultMap="accountUserMap">
select * from account
</select>
</mapper>
用户的持久层接口和映射文件
持久层接口
//根据id查询用户信息
User findById(Integer 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="com.wubo.dao.IUserDao"><!--表示映射的地址-->
<!--根据id查询用户-->
<select id="findById" parameterType="Integer" resultType="user">
select * from user where id=#{id};
</select>
</mapper>
开启Mybatis的延迟加载策略
我们需要在Mybatis的配置文件SqlMapConfig.xml文件中添加延迟加载的配置。
<!--配置参数-->
<settings>
<!--开启mybatis支持延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
编写测试只查账户信息不查用户信息
因为只将account查出来封装到account,不涉及到user,因此也就不需要查user
从结果也可以看出来只执行了从账户中查询的操作,这就是延迟操作
使用Collection实现延迟加载
同样我们也可以在一对多关系配置的<collection>结点中配置延迟加载策略。 <collection>结点中也有select属性,column属性。 需求: 完成加载用户对象时,查询该用户所拥有的账户信息。
在User实体类中加入List<Account>属性
编写用户和账户持久层接口的方法
user接口
//查询所有用户信息,同时获取到所有用户的账户信息
List<User> findAll();
account接口
//根据用户id查询账户信息
List<Account> findAccountByUid(Integer uid);
编写用户持久层映射配置
<?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.wubo.dao.IUserDao"><!--表示映射的地址-->
<!--定义user的resultMap-->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<!--配置user独享中accounts集合的映射-->
<collection property="accounts" ofType="account" select="com.wubo.dao.IAccountDao.findAccountByUid" column="id"></collection>
</resultMap>
<!--查询所有操作-->
<select id="findAll" resultMap="userAccountMap">
SELECT * FROM USER
</select>
</mapper>
注意:<collection>标签
: 主要用于加载关联的集合对象select属性
: 用于指定查询account列表的sql语句,所以填写的是该sql映射的idcolumn属性
: 用于指定select属性的sql语句的参数来源,上面的参数来自于user的id列,所以就写成id这一个字段名了
编写账户持久层映射配置
<!--根据用户id查询账户列表-->
<select id="findAccountByUid" resultType="account">
select * from account where uid=#{uid}
</select>
测试只加载用户信息
由于知识查询了用户信息,没有用到account,所以我们发现结果并没有加载Account账户信息,这也体现了延迟加载策略中的用时再进行查询
Mybatis缓存
像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。 Mybatis中缓存分为一级缓存,二级缓存。
Mybatis一级缓存
证明一级缓存的存在
一级缓存是SqlSession级别的缓存,只要SqlSession没有flush或close,它就存在。
编写用户持久层Dao接口
//根据id查询用户信息
User findById(Integer 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="com.wubo.dao.IUserDao"><!--表示映射的地址-->
<!--根据id查询用户-->
<select id="findById" parameterType="Integer" resultType="user" useCache="true">
select * from user where id=#{id};
</select>
</mapper>
编写测试方法
从结果可以看出,执行两次相同的查询,返回的地址是相同的,且控制台只执行了一次查询,说明第二次是从缓存中直接取出来的
一级缓存的分析
一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。 如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
测试一级缓存的清空
清空一级缓存,可以直接关闭,再次获取sqlSession对象及userDao;当然也可以调用清空的方法。从结果来看,两次得到结果地址不一样,是false,控制台执行了两次查询,这说明清空了缓存。
Mybatis二级缓存
二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存结构图
首先开启mybatis的二级缓存。
sqlSession1去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。
如果SqlSession3去执行相同 mapper映射下sql,执行commit提交,将会清空该 mapper映射下的二级缓存区域的数据。
sqlSession2去查询与sqlSession1相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
二级缓存的开启与关闭
第一步:在SqlMapConfig.xml文件开启二级缓存
因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。为true代表开启二级缓存;为false代表不开启二级缓存。
<!--配置二级缓存参数-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
第二步:配置相关的Mapper映射文件
在IUserDao.xml
的mapper标签中加入下列语句
<!--开启user支持二级缓存-->
<cache></cache>
第三步:配置statement上面的useCache属性
在IUserDao.xml
的mapper标签中加入下列语句,
<!--根据id查询用户-->
<select id="findById" parameterType="Integer" resultType="user" useCache="true">
select * from user where id=#{id};
</select>
将UserDao.xml映射文件中的<select>标签中设置useCache=”true”代表当前这个statement要使用二级缓存,如果不使用二级缓存可以设置为false。 注意:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。
二级缓存测试
注意:
当我们在使用二级缓存时,所缓存的类一定要实现java.io.Serializable接口,这种就可以使用序列化方式来保存对象
Mybatis注解开发
mybatis的常用注解说明
使用Mybatis注解实现基本CRUD
编写实体类
使用注解方式开发持久层接口
/**
* @author Eric
* @version 1.0
* @interfaceName com.wubo.domain.dao.IUserDao
* @description 在mybatis中针对crud一共有四个注解,@select,@insert,@update,@delete
* @date 2020-12-17 21:53:53
*/
public interface IUserDao {
/**
* @description 查询所有用户
* @param
* @return java.util.List<com.wubo.domain.User>
* @author Eric
* @date 2020/12/17 21:55
*/
@Select(value="select * from user")
List<User> findAll();
/**
* @description 保存用户信息
* @param user
* @return void
* @author Eric
* @date 2020/12/18 9:21
*/
@Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})")
void saveUser(User user);
/**
* @description 更新用户信息
* @param user
* @return void
* @author Eric
* @date 2020/12/18 9:42
*/
@Update("update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}")
void updateUser(User user);
/**
* @description 删除用户
* @param id
* @return void
* @author Eric
* @date 2020/12/18 9:46
*/
@Delete("delete from user where id=#{id}")
void deleteUser(Integer id);
/**
* @description 根据id查找用户信息
* @param id
* @return com.wubo.domain.User
* @author Eric
* @date 2020/12/18 9:49
*/
@Select("select * from user where id=#{id}")
User findById(Integer id);
/**
* @description 根据用户名进行模糊查询
* @param username
* @return java.util.List<com.wubo.domain.User>
* @author Eric
* @date 2020/12/18 9:51
*/
//username里面需要传%%
// @Select(value = "select * from user where username like #{username}")
//username里面不需要传%%
@Select("select * from user where username like '%${username}%'")
List<User> findUserByName(String username);
/**
* @description 查询总用户数量
* @param
* @return int
* @author Eric
* @date 2020/12/18 10:14
*/
@Select("select count(*) from user")
int findTotalUser();
}
编写SqlMapConfig 配置文件
同前面的配置文件,这里写一个完整版的
<?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>
<!--引入外部配置文件-->
<properties resource="jdbcConfig.properties"></properties>
<!--配置别名-->
<typeAliases>
<package name="com.wubo.domain"/>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<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>
<!--指定带有注解的dao接口所在位置-->
<!-- 配置dao接口的位置,它有两种方式 第一种:使用mapper标签配置class属性 第二种:使用package标签,直接指定dao接口所在的包 -->
<mappers>
<package name="com.wubo.domain.dao"/>
</mappers>
</configuration>
编写测试方法
这里举一个例子,其他的类似
第一部分需要获取代理对象的操作,利用注解会自动执行;第二部分,测试注解实现保存操作
public class AnnotationCRUDTest {
private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;
@Before//调用方法之前执行
public void init() throws IOException {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
}
@After//调用方法之后执行
public void close(){
session.commit();//设置提交事务
if (in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (session!=null){
session.close();
}
}
//测试保存的方法
@Test
public void testSave(){
User user = new User();
user.setUsername("Jack");
user.setAddress("上海市浦东新区");
user.setSex("男");
user.setBirthday(new Date());
userDao.saveUser(user);
}
}
使用注解实现复杂关系映射开发
实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>来实现,在使用注解开发时我们需要借助@Results注解,@Result注解,@One注解,@Many注解。
复杂关系映射的注解说明
@Results注解
/*代替的是标签<resultMap> 该注解中可以使用单个@Result注解,也可以使用@Result集合 @Results({@Result(),@Result()})或@Results(@Result())*/
@Resutl注解
/*代替了 <id>标签和<result>标签
@Result 中 属性介绍:
id 是否是主键字段
column 数据库的列名
property需要装配的属性名
one 需要使用的@One注解(@Result(one=@One)()))
many 需要使用的@Many注解(@Result(many=@many)()))*/
@One注解(一对一)
/*代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One注解属性介绍: select 指定用来多表查询的sqlmapper fetchType会覆盖全局的配置参数lazyLoadingEnabled。。 */
使用格式:@Result(column=" ",property="",one=@One(select=""))
@Many注解(多对一)
/*代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理“一对多”的关系。需要指定映射的Java实体类的属性,属性的javaType(一般为ArrayList)但是注解中可以不定义;*/
使用格式: @Result(property="",column="",many=@Many(select=""))
使用注解实现一对一复杂关系映射及延迟加载
加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)
添加User实体类及Account实体类
user类
account类
由于查询账户还需要查询用户信息,所以account类中还需要包含user类
添加账户的持久层接口并使用注解配置
/**
* @description 一对一,查询所有账户,并且获取每个账户所属的用户信息
* @param
* @return java.util.List<com.wubo.domain.Account>
* @author Eric
* @date 2020/12/18 10:48
*/
@Select("select * from account")
@Results(id = "accountMap",value = {
@Result(id = true,column="id",property = "id"),
@Result(column = "uid",property = "uid"),
@Result(column = "money",property = "money"),
//关联user,这里未采用外连接的方式,采用一对一,根据account中的uid字段去查用户信息,其中select和fetchType都是one里面的属性,
// select为查询user调用的方法,要求全限定名称,fetchType为延迟加载的类型,这里采用立即加载
@Result(property = "user",column = "uid", one = @One(select="com.wubo.domain.dao.IUserDao.findById",fetchType= FetchType.EAGER) )
})
List<Account> findAll();
添加用户的持久层接口并使用注解配置
/**
* @description 根据id查找用户信息
* @param id
* @return com.wubo.domain.User
* @author Eric
* @date 2020/12/18 9:49
*/
@Select("select * from user where id=#{id}")
@Results(id="userMap", value= { @Result(id=true,column="id",property="userId"), @Result(column="username",property="userName"), @Result(column="sex",property="userSex"), @Result(column="address",property="userAddress"), @Result(column="birthday",property="userBirthday") })
User findById(Integer id);
使用注解实现一对多复杂关系映射
需求: 查询用户信息时,也要查询他的账户列表。使用注解方式实现。 分析: 一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。
User实体类加入List<Account>
需要在用户里面加入账户信息
编写用户的持久层接口并使用注解配置
/**
* @description 一对多,查询所有用户,并且查出每个用户的账户信息
* @param
* @return java.util.List<com.wubo.domain.User>
* @author Eric
* @date 2020/12/17 21:55
*/
@Select(value="select * from user")
//将类和数据库的列对应起来(主要用于类的属性名与数据库的列名不同的情况)
@Results(id = "userMap",value = {
@Result(id = true,column = "id",property = "id"),
@Result(column = "username",property = "username"),
@Result(column = "address",property = "address"),
@Result(column = "sex",property = "sex"),
@Result(column = "birthday",property = "birthday"),
//这里的配置同一对一
@Result(property = "accounts",column = "id",many = @Many(select = "com.wubo.domain.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY))
})
List<User> findAll();
//@Many: 相当于<collection>的配置 select属性:代表将要执行的sql语句 fetchType属性:代表加载方式,一般如果要延迟加载都设置为LAZY的值
编写账户的持久层接口并使用注解配置
@Select(value = "select * from account where uid=#{uid}")
List<Account> findAccountByUid(Integer uid);
mybatis基于注解的二级缓存
在SqlMapConfig中开启二级缓存支持
<!--配置开启二级缓存-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
在持久层接口中使用注解配置二级缓存
@CacheNamespace(blocking=true)//mybatis基于注解方式实现配置二级缓存
public interface IUserDao {}