`1)商家通过入驻旅游平台,可发布相关的旅游线路信息,一个旅游线路属于一个线路分类,同时一个线路分类下可关联多个旅游线路;
`2)普通用户注册并登录平台后,可浏览商家发布的旅游线路信息;
`3)普通用户可通过收藏夹收藏相关的旅游线路;
数据库名称:travel ,库下表名定义如下:
*表名* | *描述* |
---|---|
tab_user | 用户表 |
tab_route | 路线表 |
tab_favorite | 收藏表 |
tab_category | 分类表 |
tab_seller | 商家表 |
用户表 tab_user 用户信息的定义
*字段* | *类型* | *长度* | *是否为空* | *注释* |
---|---|---|---|---|
id | bigint | 18 | N | 主键 |
real_name | varchar | 200 | N | 姓名 |
telephone | varchar | 12 | N | 电话 |
sex | char | 200 | Y | 性别 |
username | varchar | 100 | N | 账户 |
password | varchar | 32 | N | 密码 |
birthday | datetime | 0 | Y | 生日 |
varchar | 100 | Y | 邮箱 |
路线表 tab_route 路线信息表定义
*字段* | *类型* | *长度* | *是否为空* | *注释* |
---|---|---|---|---|
id | bigint | 18 | N | 主键 |
route_name | varchar | 200 | N | 线路名称 |
price | decimal | (12,4) | N | 价格 |
route_Introduce | varchar | 2000 | Y | 线路描述 |
flag | char | 1 | N | 标记 |
is_theme_tour | char | 1 | N | 是否主题之旅 |
attention_count | int | 11 | N | 关注数 |
category_id | bigint | 18 | N | 分类ID |
seller_id | bigint | 18 | N | 商家ID |
收藏表 tab_favorite 用户收藏的线路关联表
*字段* | *类型* | *长度* | *是否为空* | *注释* |
---|---|---|---|---|
id | bigint | 18 | N | 主键 |
user_id | bigint | 18 | N | 用户Id |
route_id | bigint | 18 | N | 路线Id |
分类表 tab_category线路信息的分类管理
*字段* | *类型* | *长度* | *是否为空* | *注释* |
---|---|---|---|---|
id | bigint | 18 | N | 主键 |
category_name | varchar | 100 | N | 分类名称 |
category_url | varchar | 255 | N | 分类链接 |
商家表 tab_seller商家信息的定义,商家登录后台时的账户信息
*字段* | *类型* | *长度* | *是否为空* | *注释* |
---|---|---|---|---|
id | bigint | 18 | N | 主键 |
seller_name | varchar | 200 | N | 商家姓名 |
telephone | varchar | 12 | N | 电话 |
address | varchar | 200 | Y | 地址 |
username | varchar | 100 | N | 账户 |
password | varchar | 32 | N | 密码 |
birthday | datetime | 0 | Y | 生日 |
根据以下要求,编写SQL(操作过程中**可以往表中自由添加测试数据,校验SQL)
1) 按照业务需求定义商家表,用户表,线路表,线路分类和收藏表(SqlYog可视化操作导出SQL或手写DDL皆可)
2) 给用户表中telephone添加唯一约束(SqlYog可视化操作导出SQL或手写DDL皆可)
3) 使用SQL向用户表全字段批量插入2条测试数据
4) 使用SQL完成user_id为2的用户对route_id为2的线路的收藏(insert语句)与取消收藏(delete语句)功能
5) 使用select语句查询姓名为'XXX'的用户收藏过哪些旅游线路(显示线路的所有字段信息)
6) 使用SQL统计每个线路分类下各有多少旅游线路数量(显示线路分类名称,数量)
7) 使用SQL统计每个线路分类下用户的收藏数量,并倒叙排序(查询字段包含线路分类名称,数量)
补充:线路分类下对应的线路收藏数如果为0,则不显示;
8) 使用SQL查询id为2的用户收藏过哪些旅游线路,包含线路表所有字段,线路分类名称和商家电话
9) 使用SQL根据商家名称(比如seller_name = 'XXX')查询其发布的旅游线路有哪些用户收藏(显示用户表所有字段信息)(5分)
搭建Mybatis开发环境
导入依赖相关依赖jar,配置Mybatis核心配置文件
定义pojo类:User,Category,Favorite,Route,Seller;
创建相关接口,并与xml映射文件绑定
加载核心配置文件,完成业务功能
1) 定义好各个Pojo类,mapper接口和映射文件,完成根据用户id(比如id=1)查询用户信息功能(10分)
2) 使用Mybatis完成用户注册功能(insert插入)(5分)
3) 使用Mybatis完成user_id为1的用户对route_id为6的线路的收藏(insert语句)与取消收藏(delete语句)功能(5分)
4) 使用Mybatis根据用户名(比如张三)查询其收藏过哪些旅游线路(显示线路的所有字段信息)(5分)
5) 使用Mybatis统计每个线路分类下各有多少旅游线路数量(查询字段包含线路分类名称,数量)(5分)
6) 使用Mybatis统计每个线路分类下用户的收藏数量,并降序排序(查询字段包含线路分类名称,收藏数量)(5分)
7) 使用Mybatis查询id为2的用户收藏过哪些旅游线路,,包含线路表所有字段,线路分类名称和商家电话(5分);
8) 使使用Mybatis根据商家名称(比如:xxx)查询其发布过的旅游线路有哪些用户收藏(5分)
Mybatis为用户提供了快速的开发方式,因为有时候大量的XML配置文件的编写时非常繁琐的,
因此Mybatis也提供了更加简便的基于注解(Annnotation)的配置方式。
注解配置的方式在很多情况下能够取代mybatis的映射文件,提高开发效率。
@Insert:保存
Value:sql语句(和xml的配置方式一模一样)
@Update:更新
Value:sql语句
@Delete: 删除
Value:sql语句
@Select: 查询
Value:sql语句
@Options:可选配置(获取主键)
userGeneratedKeys:开关,值为true表示可以获取主键 相当于select last_insert_id()
keyProperty :对象属性
keyColumn : 列名
【第一步】将mybatis全局配置文件mybatis‐config.xml中的mapper路径改为包扫描或者class路径
说明:因为没有了映射文件,所以我们这里采用加载接口方式,
需要告知mybatis哪个接口的方法上的注解需要被执行。
【第二步】编写接口和注解
【第三步】测试
代码演示
1) 配置核心配置文件
<mappers>
<!--基于扫包方式加载mapper接口-->
<package name="com.day08.mapper"/>
</mappers>
2) 定义接口
public interface UserMapper {
@Insert("insert into tb_user values(null,#{userName},#{password},#{name}," +
"#{age},#{sex})")
Integer addUser(User user);
@Delete("delete from tb_user where id=#{id}")
Integer deleteByUserId(@Param("id") Integer id);
@Update("update tb_user set user_name=#{userName},password=#{password}," +
"name=#{name},age=#{age},sex=#{sex} where id=#{id}")
Integer updateUser(User user);
@Select("select * from tb_user")
List<User> findAll();
}
3) 测试
public void test(){
UserMapper userMapper = MybatisUtil.getMapper(UserMapper.class);
........
}
1) 接口
@Insert("insert into tb_user values(null,#{userName},#{password},#{name}," +
"#{age},#{sex})")
@Options(useGeneratedKeys = true,keyColumn = "id" ,keyProperty = "id")
Integer addUserAndGetFk(User user);
2) 测试
@Test
public void test6(){
UserMapper mapper =
MybatisUtil.getMapper(UserMapper.class);
User user = new User();
user.setAge(18);
user.setName("小明");
user.setSex(0);
user.setUserName("xiaoming");
user.setPassword("12345");
mapper.addUserAndGetFk(user);
System.out.println(user.getId());
MybatisUtil.commit();
MybatisUtil.close();
}
如果数据表的列名和pojo实体类的属性名不一致,会导致数据表的数据无法封装到实体类属性值中,
对此我们有如下解决方案:
public @interface Results {
Result[] value() default {
};
}
// value属于Result数组类型,而Result属于一个注解,注解的属性如下:
public @interface Result {
//对应数据表的列
String column() default "";
//对应pojo类的属性
String property() default "";
//javaType:返回的对象类型
Class<?> javaType() default void.class;
//one: 一对一配置 相当于xml中association标签
One one() default @One;
//many: 一对多配置 相当于xml中collection标签
Many many() default @Many;
}
目标:使用注解的方式给取别名后的字段,映射到实体类中,并查询所有用户信息;
注意:为方便演示效果,可将之前核心配置文件中的开启驼峰自动映射设置为false。
1) 接口
@Select("select * from tb_user where id=#{id}")
@Results(id="userMap",
value = {
@Result(column = "id",property = "id",id=true),
@Result(column = "user_name",property = "userName")
})
User findById(Long id);
2) 测试
@Test
public void test5(){
UserMapper userMapper = MybatisUtil.getMapper(UserMapper.class);
User user = userMapper.findById(1L);
System.out.println(user);
MybatisUtil.close();
}
在接口上添加: 标签,标签体中编写sql语句,
sql语句与之前XML配置文件中的一致即可;
这种方式在写法上面和 XML 中的写法是一样,支持 XML 的动态SQL语法,可以在上面的字符串中
写 等 标签的语法。
1) 定义接口
@Select("<script>
select * from tb_user where sex=1\n" +
" \n" +
" and user_name like concat('%',#{uName},'%')\n" +
" </if>
</script>")
@ResultMap(value = "userMap")
List<User> findUserByName(@Param("uName") String name);
使用 @SelectProvider 注解,注解中的type 参数是提供构建 SQL 的类,method 是构建 SQL 的方法。
构建 SQL 的方法的参数要和接口的参数一致,并且多个参数要使用@Param命名参数。
1)接口
@SelectProvider(type = SqlProvider.class,method = "findUserByName")
@ResultMap(value = "userMap")
List<User> findUserByName2(@Param("uName") String name);
2)创建提供sql的工具类
public class SqlProvider {
public String findUserByName(@Param("uName") String name){
String sql="select * from tb_user where sex=1";
if(name!=null){
sql+=" and user_name like concat('%',#{uName},'%')";
}
return sql;
}
}
上述实现过程中,sql拼接容易出错,
我们可借助mybatis提供的一个对象:SQL完成sql语句拼接。
创建SQL对象 SQL sql = new SQL();
链式编程, 每个方法返回值都是SQL类对象;
1) 定义接口
@SelectProvider(type = SqlProvider.class,method = "findUserByName3")
@ResultMap(value = "userMap")
List<User> findUserByName3(@Param("uName") String name);
2) 定义提供sql的类
public String findUserByName3(@Param("uName") String name){
SQL sql = new SQL();
sql = sql.SELECT("*").FROM("tb_user").WHERE("sex=1");
if(name!=null){
sql = sql.AND().WHERE("user_name like concat('%',#{uName},'%')");
}
return sql.toString();
}
测试
@Test
public void test8(){
UserMapper mapper = MybatisUtils.getMapper(UserMapper.class);
//select * from tb_user where sex=1;
// List users = mapper.findUserByName(null);
//select * from tb_user where sex=1 and user_name like concat('%',?,'%')
List<User> users = mapper.findUserByName("zhang");
System.out.println(users);
MybatisUtils.close();
}
注意:使用注解方式实现多表查询只能按照分步查询,不能使用连接查询。
一对一映射核心映射注解:
@Results(value = {
//主键属性
@Result(column = "xx",property = "xx",id = true),
//普通属性
@Result(column = "xx",property = "xx"),
//一对一关联查询
@Result(column = "xx",property = "xx",javaType = xx.class,one = @One(select = "方法全限定名"))
})
【需求】注解方式实现,查询订单编号20140921003订单信息,并查询出下单人信息;
【步骤】
【SQL分析】
-- 基于xml:
select tb_user.*,tb_order.id as order_id,tb_order.order_number from tb_user,tb_order where tb_user.id=tb_order.user_id
and tb_order.order_number='20140921003';
-- 如果基于注解开发,不同的表下的数据,必须分段加载
-- 查询订单信息
select * from tb_order where order_number='20140921003'; -- user_id:1
-- 查询用户信息
select * from tb_user where id=1;
1)定义根据id查询用户的接口
@Select("select * from tb_user where id=#{userId}")
@ResultMap(value = "userMap")
User findById(@Param("userId") Long id);
2)定义根据订单标号查询订单的接口
public interface OrderMapper {
@Select("select * from tb_order where order_number=#{orderNumber}")
@Results(value = {
@Result(column = "id",property = "id",id = true),
@Result(column = "order_number",property = "orderNumber"),
@Result(column = "user_id",
one = @One(select =
"com.heima.mapper.UserMapper.findById"),
property = "user"
)})
Order findByOrderNumber(@Param("orderNumber") String orderNumber);
}
3)测试
@Test
public void test11(){
OrderMapper orderMapper = MybatisUtils.getMapper(OrderMapper.class);
Order order = orderMapper.findByOrderNumber("20140921003");
System.out.println(order);
MybatisUtils.close();
}
4)效果
一对多核心映射配置:
@Results(value = {
//主键映射
@Result(column = "xx",property = "xx",id=true),
//普通字段映射
@Result(column = "xx",property = "xx"),
//一对多关联查询
@Result(column = "xx",property = "xx",javaType = List.class,many = @Many(
select = "从表查询集合方法的全限定名称"
))
})
需求:查询id为1的用户及其订单信息 分步查询
说明:查询id是1的用户,一个用户可以有多个订单。所以用户和订单是一对多关系。
第一步:根据需求书写分步查询的sql语句;
-- 查询id为1的用户及其订单信息 分步查询
select * from tb_user where id=1;
-- 根据用户id查询订单集合
select * from tb_order where user_id=1;
第二步:给OrderMapper接口添加根据用户ID查询订单信息的方法;
@Select("select * from tb_order where user_id=#{uId}")
@Results(value = {
@Result(column = "id",property = "id",id=true),
@Result(column = "order_number",property = "orderNumber"),
})
List<Order> findOrdersByUserId(@Param("uId") Long userId);
第三步:在UserMapper接口添加根据id查询用户数据,并完成一对多映射配置;
@Select("select * from tb_user where id=#{uId}")
@Results(id="userInfoMap",
value = {
@Result(column = "id",property = "id",id=true),
@Result(column = "user_name",property = "userName"),
@Result(column = "id",
many = @Many(select = "com.day08.mapper.OrderMapper.findOrdersByUserId"),
property = "orders"
)
})
User findUserInfoByUserId(@Param("uId") Long userId);
第四步:测试;
@Test
public void test13(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
User user = mapper.findUserInfoByUserId(1L);
System.out.println(user);
MybatisUtil.close();
}
第一步:完成根据订单号查询订单及下单人(一对一)的注解查询;
第二步:在注解中添加延迟加载
1.因为基于xml的延迟加载配置作用域是全局的,对所有的接口都生效,所以为方便演示可将全局配置文件:
mybatis-config.xml中延迟加载配置注释掉;
<!--开启延迟加载-->
<!--<setting name="lazyLoadingEnabled" value="true"/>-->
<!--关闭积极加载-->
<!--<setting name="aggressiveLazyLoading" value="false"/>-->
2.懒加载配置在@One和@Many注解中都存在;
@Select("select * from tb_order where order_number=#{orderNumber}")
@Results(value = {
@Result(column = "id",property = "id",id=true),
@Result(column = "order_number",property = "orderNumber"),
//1)column = "user_id":获取user_id字段对应的值
//2)one = @One(select = "com.day08.mapper.UserMapper.findById"):表示获取的字段user_id的值传给这个方法
//3)property = "orderUser":表示将第二步中方法执行返回的结果赋值给orderUser变量
@Result(column = "user_id" ,
one = @One(select = "com.day08.mapper.UserMapper.findById",fetchType = FetchType.LAZY),
property = "orderUser")
})
Order findByOrderNumber(@Param("orderNumber") String orderNumber)
3.测试
@Test
public void test10(){
OrderMapper mapper = MybatisUtil.getMapper(OrderMapper.class);
Order order = mapper.findByOrderNumber("20140921003");
System.out.println(order.getOrderNumber());
System.out.println("开始使用用户信息");
System.out.println(order.getOrderUser());
MybatisUtil.close();
}
缓存就是用来提高查询访问速度,就是将每次的访问记录缓存在一地方,在下次进行查询时首先访问的不是数据库,而是缓存,如果在缓存中查询到了该次查询条件的结果集就直接返回结果,不再访问数据库。这样就对数据库的操作次数上就减少了很多,从而提高访问速度。
在查询缓存的机制中有两种不同的作用域(生命周期),分别是一级缓存与二级缓存
MyBatis的一级查询缓存是由 org.apache.ibatis.cache.impl.PerpetualCache 类的 HashMap本地缓存实现的,它的作用域则是SqlSession,它的作用域也就是生命周期;
假如同一个SqlSession中执行两次sql语句的查询,这两次的查询的位置是不同的,第一次查询时,由于没有缓存结果则是从数据库中进行查询结果,得到结果后写入缓存并将结果返回给查询语句,而在进行第二次查询时,这时缓存中已经有符合条件的结果集,这次的查询就会在缓存获得结果并返回,而不会向数据库进行查询。当SqlSession结束后相应的缓存也就销毁了。
myBatis默认一级查询时开启状态;
1.概念:一级缓存属于本地缓存,SqlSession级别的;
2.原理:
1)在同一个SqlSession,发生了一次查询,查询的结果会存入一级缓存;
2)第二次再发生相同查询时,直接从缓存中获取数据,不再与数据库建立连接查询数据;
3.注意:一级缓存,默认是开启的,无法关闭。
1) 测试一级缓存
需求:完成根据id查询用户数据;
测试步骤:用同一个session根据id查询用户2次,然后查看日志打印sql的情况进行判断判断;
1.在相同的sqlsession下调用mapper接口执行2次查询,第二次数据从一级缓存中获取;
/**
* 测试一级缓存
*/
@Test
public void test14(){
UserMapper userMapper = MybatisUtil.getMapper(UserMapper.class);
//1.根据用户ID查询用户信息 一级缓存中没有数据,那么就会去数据库查询(发送sql)
User user = userMapper.findById(1L);
//2.相同的条件再次查询,就会被一级缓存名称,不发送sql到mysqlserver
User user2 = userMapper.findById(1L);
System.out.println(user);
System.out.println(user2);
}
2.开启两个session,调用相同的方法,查看sql执行次数;
public class TestAll2 {
private static SqlSession session1;
private static SqlSession session2;
static {
InputStream in = null;
try {
in = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
session1=sessionFactory.openSession();
session2=sessionFactory.openSession();
}
@Test
public void test1(){
UserMapper mapper1 = session1.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user1 = mapper1.findById(1l);
User user2 = mapper2.findById(1l);
System.out.println(user1);
System.out.println(user2);
}
}
说明:一级缓存是session级别,sessin与session之间不能共享缓存;
3.一级缓存失效的情况
①查询条件不一致:
说明:相同的session下可根据id=1和id=2去查询,发现需要查询2次;
@Test
public void test3(){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findById(1l);
System.out.println(user);
//说明:两次查询,条件不一样,不会被缓存名称,此时应该打印两次sql
User user2 = userMapper.findById(2l);
System.out.println(user2);
}
②sqlSession不同;
说明:获取两个sesion,然后在两个sesion中获取相同类型的动态代理对象,进行测试;
③两次查询期间存在增删改操作;
说明:在执行update、insert、delete的时候,即使操作的不是和一级缓存中的是同一条记录,都会清空一级缓存。
/**
* 测试一级缓存失效
*/
@Test
public void test15(){
UserMapper userMapper = MybatisUtil.getMapper(UserMapper.class);
//1.根据用户ID查询用户信息 一级缓存中没有数据,那么就会去数据库查询
(发送sql)
User user = userMapper.findById(1L);
//2.当当前session发生增删改操作,就会清除一级缓存
userMapper.deleteByUserId(13);
//3.此时因为一级缓存中数据被清空了,所以需要从数据库再次查询
User user2 = userMapper.findById(1L);
System.out.println(user);
System.out.println(user2);
}
④手动清除一级缓存;
使用:sqlSession.clearCache();可以强制清除缓存;
@Test
public void test6(){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//共打印2次sql
User user = userMapper.findById(12l);
System.out.println(user);
//清空当前session下的一级缓存
sqlSession.clearCache();
//此时需要再次去数据库查询
User user2 = userMapper.findById(12l);
System.out.println(user2);
}
概念:全局缓存 ,namespace级别;
mybatis 的二级缓存的作用域:mapper范围的(即映射文件级别的,多个sqlSession可以共享二级缓存数据);
原理:
在一个sqlSession,进行了一次查询,会把查询结果存入一级缓存;
当sqlSession关闭之后,mybatis会把一级缓存中的数据存入二级缓存中;
当我们再次发生相同的查询时,会从二级缓存中命中;
场景:
在用户通过id查询一个商品信息,执行完查询方法之后会将sqlSession释放掉,当再次发送请求查询同一个商品信息时会再次获取一个sqlSession来执行查询,那么此时如果配置了二级缓存的话,就可以直接从二级缓存中获取信息,而不用再次去数据库查询了。
二级缓存条件:
1、手动开启:
1.全局配置中: 默认已开启。
映射文件中:
2.注解方式:或者在接口上添加注解 @CacheNamespace
2、第一个session必须关闭;
说明:由于缓存数据是在sqlSession调用close方法时,放入二级缓存的,
因此在测试二级缓存时必须先将第一个sqlSession关闭;
3、二级缓存的对象必须序列化,例如:User对象必须实现Serializable接口。
说明:因为二级缓存的原理就是将对象进行序列化
测试二级缓存
开启二级缓存方式: 在映射文件(UserMapper.xml)中添加<cache />;
或者在Mapper接口添加@CacheNamespace注解开启;
测试思路:session1获取mapper进行查询,然后关闭session1,session2获取mapper相同条件二次查询;
1)在核心配置文件下开启二级缓存
<!--开启驼峰映射-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
2)接口下标注开启namespace二级缓存
@CacheNamespace(blocking = true)
public interface UserDao {
.....
}
3)测试
/**
二级缓存测试
测试思路:session1获取mapper进行查询,然后关闭session1,session2获取mapper相同条件二次查询;
*/
@Test
public void test2(){
UserMapper mapper1 = session1.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
//1.session1根据id=1查询数据,查询完毕数据存入一级缓存
User user1 = mapper1.findById(1l);
//2.session1调用close方法,会将session1的一级缓存数据存储到二级缓存下
session1.close();
//3.session2根据id=1查询用户信息,发现没有走数据库查询,走的是二级缓存
User user2 = mapper2.findById(1l);
System.out.println(user1);
System.out.println(user2);
}
二级缓存失效的情况
/**
二级缓存测试失效
第二个sqlSession在执行update、insert、delete的时候,也同样会清空二级缓存中的内容。
*/
@Test
public void test3(){
UserMapper mapper1 = session1.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
//1.session1根据id=1查询数据,查询完毕数据存入一级缓存
User user1 = mapper1.findById(1l);
//2.session1调用close方法,会将session1的一级缓存数据存储到二级缓存下
session1.close();
//3.session2进行增删改操作,会清空二级缓存
mapper2.deleteByUserId(13);
//4.session2根据id=1查询用户信息,发现二级缓存中没有数据,那么从数据库查询
User user2 = mapper2.findById(1l);
System.out.println(user1);
System.out.println(user2);
}