日志的使用
我们在使用MyBatis的时候, 其实MyBatis框架会打印一些必要的日志信息, 在开发阶段这些日志信息对我们分析问题,理解代码的执行是特别有帮助的; 包括项目上线之后,我们也可以收集项目的错误日志到文件里面去; 所以我们采用专门的日志系统来处理.
步骤
- 导入坐标
- 拷贝日志配置文件到项目
讲解
• 导入坐标
dart
<!--log start-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.6</version>
</dependency>
<!--log end-->
• log4j.properties
dart
#日志输出级别,最低级别为DEBUG,输出到stdout
#error > warn > info > debug > trace
log4j.rootLogger=DEBUG,stdout
#输出到控制台
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#可以灵活的指定布局模式
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#时间
log4j.appender.stdout.layout.ConversionPattern=[%-5p] %t %l %d %rms:%m%n
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=D:\\test\\mm_backend.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS\} %-5p [%t] {%c}-%m%n
log4j.rootLogger=INFO, stdout , R
此句为将等级为 INFO 的日志信息输出到 stdout 和 R 这两个目的地,stdout 和 R 的定义在下面的代码,可 以任意起名。等级可分为 OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL,如果配置 OFF 则不打出任何信息, 如果配置为 INFO 这样只显示 INFO, WARN, ERROR 的 log 信息,而 DEBUG 信息不会被显示,具体讲解可参照 第三部分定义配置文件中的 logger。
使用Mybatis完成CRUD
需求
使用Mybatis完成CRUD
新增用户
步骤:
AspectJ 切面注解中五种通知注解:@Before、@After、@AfterRunning、@AfterThrowing、@Around
https://blog.csdn.net/u010502101/article/details/78823056
- UserDao中添加新增方法
dart
public interface UserDao {
/**
* 添加用户
* @param user 要添加到数据库的数据
* @return 受到影响的行数
*/
public int addUser(User user);
}
- 在UserDao.xml中加入新增配置
dart
<insert id="addUser" parameterType="User">//因为配置文件里起了别名,所以这里直接User就行了,不然还是要写全路径。
insert into t_user (username,sex,birthday,address) values (#{username},#{sex},#{birthday},#{address})//占位符见下面根据id查询的xml里面有说明。
</insert>
- 添加操作测试
dart
public class TestMybatis {
private UserDao userDao;
private SqlSession sqlSession;
private InputStream is;
@Before//用这个注解,执行@Test之前会先执行这个方法
public void init() throws IOException {
//1.让mybatis框架去加载主配置文件
//1.1 将主配置文件SqlMapConfig.xml转换成字节输入流
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//1.2创建一个SqlSessionFactoryBuilder
SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
//1.3使用factoryBuilder对象加载字节输入流,创建 SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = factoryBuilder.build(is);
//1.4使用sqlSessionFactory对象创建处SqlSession对象
sqlSession = sqlSessionFactory.openSession();//使用了工厂模式
//2.使用sqlSession对象创建处UserDao接口的代理对象
userDao = sqlSession.getMapper(UserDao.class);//使用了动态代理
}
@Test
public void testAddUser(){
//3.调用userDao代理对象的addUser()方法添加用户
User user = new User(null,"zl","男",new Date(),"成都");//注意!这里是null,pojo里面id必须为Integer类型!int类型不能为null。
userDao.addUser(user);
//目标是添加完用户之后,获取到该用户的id,可以在以后的多表中,进行关联查询
System.out.println(user.getUid());//这个说明见下面这个
}//获取从pojo获取
@After//同理,@Test之后执行这个。
public void destroy() throws IOException {
//提交事务
sqlSession.commit();
//4.关闭资源
sqlSession.close();
is.close();
}
}
新增用户 id 的返回值
新增用户后, 同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长 auto_increment 的值返回。
• SelectKey获取主键
dart
<!--
我们可以发现, 这个 sql 语句中使用#{}字符, #{}代表占位符,我们可以理解是原来 jdbc 部分所学的?,它们都是代表占位符, 具体的值是由 User 类的属性来决定的,前提必须名字pojo里面的名字和占位符里面的一样,才会给匹配上去。然后sql前面的名字是和数据库里字段名匹配。
parameterType 属性:代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。
selectKey标签: 查询主键
keyColumn 表示要查询的列名
keyProperty 表示要赋值的属性名,返回的赋值给pojo了
resultType 表示查询出来的结果的类型
order 表示在前或者在后执行
select last_insert_id() 查询最后一个自增长的id的值
-->
<insert id="addUser" parameterType="User">
<selectKey resultType="int" order="AFTER" keyProperty="uid" keyColumn="uid">
select last_insert_id()
</selectKey>
insert into t_user (username, sex, birthday, address) values (#{username}, #{sex}, #{birthday}, #{address})
</insert>
根据id查询用户(会被修改方法调用)
UserDao添加根据id查询的方法
public User findById(Integer id);
UserDao.xml文件中新增配置
dart
<select id="findById" parameterType="int" resultType="User">
select * from t_user where uid=#{uid}
</select>
修改用户(测试时会调用根据Id查询方法)
UserDao添加修改的方法
public void updateUser(User user);
UserDao.xml文件中新增配置
dart
<update id="updateUser" parameterType="User">
update t_user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where uid=#{uid}
</update>
测试:
先调用根据id查询要修改的人的信息,再修改。所以,xml文件里应该修改所有的信息,在这里不能单个修改。
删除用户
UserDao中添加删除方法
public int deleteById(int id);
UserDao.xml文件中新增配置
dart
<delete id="deleteById" parameterType="int">
delete from t_user where uid=#{id}
</delete>
注意:
数据库删除中间的任意一行后,比如删除第3个。原来顺序是1234,会变成124。就是不是一个完整的顺序了!!!如果想调整成完整的顺序,就需要别的操作来操作一下。
模糊查询
方式一(不推荐使用)
UserDao中添加模糊查询方法
public List searchByUsername(String name);
UserDao.xml文件中新增配置
dart
<select id="searchByUsername" parameterType="String" resultType="User">
select * from t_user where username like #{name}
</select>
测试方法
查询名字里有l的。
dart
@Test
public void testSearchByUsername(){
List<User> userList = userDao.searchByUsername("%l%");
for (User user : userList) {
System.out.println(user);
}
}
方式二
UserDao中添加模糊查询方法
public List searchByUsername(String name);
UserDao.xml文件中新增配置
dart
<!--
模糊查询
另外一种在SQL语句中引用方法的参数的方式:${}
1. 引用pojo中的属性: '${属性名}'
2. 引用简单类型的参数: '${value}',但是高版本的mybatis中可以'${任意字符串}'
-->
<select id="searchByUsername" parameterType="String" resultType="User">
方式一:<!--select * from t_user where username like "%"#{name}"%"-->
方式二:<!--select * from t_user where username like concat("%",#{name},"%")-->
select * from t_user where username like "%${value}%"
//方式三:才是最常用。
</select>
测试方法
dart
@Test
public void testSearchByUsername(){
List<User> userList = userDao.searchByUsername("l");//上面三个都是传一个l就行。
for (User user : userList) {
System.out.println(user);
}
}
#{}与${}的区别
- #{}一定不能写在引号里面,${}一定要写在引号里面,因为他是字符串拼接
- 如果是pojo、map类型的参数,无论是#{}还是${}里面都是属性名
- 如果是简单类型的参数,#{}里面可以写任意字符串,但是${}里面只能写value(以前的版本)
- 如果使用#{}引入参数的话,其实是先使用?这个占位符,然后再设置参数;而使用${}引入参数的话,是直接拼接SQL语句
建议尽量使用#{}。
SqlSessionFactory工具类的抽取
上面是使用前置通知和后置通知@Before和@After,我们也可以把这些相同的步骤放在同一个工具类Utils里。
步骤:
- 创建SqlSessionFactoryUtils
- 定义一个openSqlSession ()方法获得sqlSession
- 定义释放资源方法
- 保证SqlSessionFactory只有一个(静态代码块)
实现:
dart
package com.gavin.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* 1. 类加载的时候就创建出来了一个SqlSessionFactory对象
* 2. 我们得声明一个方法,用于创建SqlSession对象,因为每次执行SQL语句的sqlSession对象不能是同一个
*/
public class SqlSessionFactoryUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try{
//1 将主配置文件SqlMapConfig.xml转换成字节输入流
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2创建一个SqlSessionFactoryBuilder
SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
//3使用factoryBuilder对象加载字节输入流,创建 SqlSessionFactory对象
sqlSessionFactory = factoryBuilder.build(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 创建SqlSession对象
* @return
*/
public static SqlSession openSqlSession(){
return sqlSessionFactory.openSession();
}
/**
* 提交事务并且关闭SqlSession
* @param sqlSession
*/
public static void commitAndClose(SqlSession sqlSession){
sqlSession.commit();
sqlSession.close();
}
/**
* 回滚事务并且关闭SqlSession
* @param sqlSession
*/
public static void rollbackAndClose(SqlSession sqlSession){
sqlSession.rollback();
sqlSession.close();
}
}
测试
dart
public class TestSqlSessionFactoryUtils {
@Test
public void test1(){
//1. 创建SqlSession对象
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
//2. 创建UserDao的代理对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
//3. 调用userDao对象的findById()方法
System.out.println(userDao.findById(6));
//4. 关闭资源,提交事务
SqlSessionFactoryUtils.commitAndClose(sqlSession);
}
}
parameterType深入
传递简单类型
单个参数,方法就以简单类型传入即可,那么在映射配置文件中的parameterType的值就是这个简单类型的别名;在SQL语句中引入简单类型的参数#{任意字符串}
User findById(int id);
select * from t_user where uid=#{id}
传递 pojo 对象 或者 Map
- 将多个参数封装到一个POJO中,那么在映射配置文件中parameterType的值就是这个POJO的全限定名或者别名; 在SQL语句中引入参数#{POJO的属性名}或者'${POJO的属性名}'
- 将多个参数封装到一个Map中(要封装的参数没有对应的POJO),那么在映射配置文件中parameterType的值是map; 在SQL语句中引入参数#{map的key}或者'${map的key}'
dart
void addUser(User user);
void updateUser(Map map);
<insert id="addUser" parameterType="User">
insert into t_user (username, sex, birthday, address) values (#{username}, #{sex}, #{birthday}, #{address})
</insert>
<update id="updateUser" parameterType="map">
update t_user set username=#{username},sex=#{sex} where uid=#{uid}
</update>
@Test
public void test2() {
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
User user = new User(null, "麻子", "男", new Date(), "重庆");
mapper.addUser(user);
SqlSessionFactoryUtils.commitAndClose(sqlSession);
}
@Test
public void test3(){
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
Map map = new HashMap();
map.put("username","老八");
map.put("sex","女");
map.put("uid",7);
mapper.updateUser(map);
SqlSessionFactoryUtils.commitAndClose(sqlSession);
}
传递多个参数
方式一:通过参数索引
dart
User findByUsernameAndAddress(String username, String address);
<select id="findByUsernameAndAddress" resultType="User">
select * from t_user where username=#{arg0} and address=#{arg1}
</select>
方式二:通过注解
@Param使用注解指定参数的名称
dart
User findByUsernameAndSex(@Param("name") String username,@Param("sex") String sex);
<select id="findByUsernameAndSex" resultType="User">
select * from t_user where username=#{name} and sex=#{sex}
</select>
传递 pojo 包装对象类型
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。Pojo 类中包含 pojo。
需求:根据用户id查询用户信息并进行分页,查询条件放到 QueryVo 的 user 属性中。
QueryVo.java
dart
@Data
@AllArgsConstructor
@NoArgsConstructor
public class QueryVo {
public QueryVo(Long currentPage, Integer pageSize, User queryCondition) {
this.currentPage = currentPage;
this.pageSize = pageSize;
this.queryCondition = queryCondition;
}
private Long currentPage;
private Integer pageSize;
//查询条件
private User queryCondition;
private Long offset;
public Long getOffset(){
return (currentPage-1)*pageSize;
}
}
UserDao.java
dart
List<User> searchByCondition(QueryVo queryVo);
UserDao.xml
dart
<select id="searchByCondition" parameterType="QueryVo" resultType="User">
select * from t_user where sex=#{queryCondition.sex} and address=#{queryCondition.address}
limit #{offset},#{pageSize}
</select>
测试
dart
@Test
public void test6() {
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
User user = new User();
user.setSex("男");
user.setAddress("北京");
QueryVo queryVo = new QueryVo(1l,5,user);
List<User> userList = mapper.searchByCondition(queryVo);
for (User user1 : userList) {
System.out.println(user1);
}
SqlSessionFactoryUtils.commitAndClose(sqlSession);
}
resultType深入
输出简单类型
查询的结果是单个数据, 映射配置文件中的resultType属性的值就是这个数据的类型
dart
/**
* 查询用户的总个数
* @return
*/
Long findTotal();
<select id="findTotal" resultType="long">
select count(*) from t_user
</select>
输出pojo对象(一个pojo对象就对应一行数据)或者一个Map
查询的结果是一行数据:
• 将这一行数据存储到POJO对象中, 映射配置文件的resultType的值就是POJO的全限定名或者别名,此时就要求查询结果的字段名和类型 要和POJO的属性名和类型一致
dart
/**
* 根据id查询一条数据
* @param id
* @return
*/
User findById(int id);
<select id="findById" parameterType="int" resultType="User">
select * from t_user where uid=#{uid}
</select>
• 将这一行数据存储到Map对象,映射配置文件的resultType的值就是map,那么此时查询结果中的字段名就是 map的key,字段值就是map的value
/**
- 根据用户名查询用户
- @param username
- @return
*/
Map findByUsername(String username);
select * from t_user where username = #{username}
输出pojo列表(一个pojo列表就对应多行数据)或者Map的列表
查询的结果是多行数据:
• 将多条数据存储到List中,映射配置文件的resultType的值就是POJO的别名
dart
List<User> findAll();
<select id="findAll" resultType="User">
select *
from t_user
</select>
• 将多条数据存储到List中,映射配置文件的resultType的值就是map
dart
List<Map> findAll2();
<select id="findAll2" resultType="map">
select *
from t_user
</select>
resultMap结果类型
resultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。
如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系 ,resultMap实质上还需要将查询结果映射到pojo对象中。
resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。(下次课讲)
那我们今天就来看返回的列名与实体类的属性不一致时的情况. 下次课再接着研究复杂的封
UserInfo
dart
package com.gavin.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo implements Serializable {
private Integer userId;
private String username;
private String userSex;
private String userBirthday;
private String userAddress;
}
UserDao
dart
/**
* 查询所有用户信息,封装到UserInfo对象
* @return
*/
List<UserInfo> findAllUserInfo();
UserDao.xml
dart
<resultMap id="userInfoMap" type="UserInfo" autoMapping="true">
Column是数据库字段名
<id column="uid" property="userId"></id>
<result column="sex" property="userSex"></result>
<result column="birthday" property="userBirthday"></result>
<result column="address" property="userAddress"></result>
</resultMap>
<select id="findAllUserInfo" resultMap="userInfoMap">
select *
from t_user
</select>
测试
dart
public class TestMybatis {
@Test
public void test1() {
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<UserInfo> userInfoList = mapper.findAllUserInfo();
for (UserInfo userInfo : userInfoList) {
System.out.println(userInfo);
}
SqlSessionFactoryUtils.commitAndClose(sqlSession);
}
}
Mybatis 映射文件的 SQL 深入(重点)
动态 SQL 之 if标签
我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。
比如在 id 如果不为空时可以根据 id查询,如果 username 不为空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到
注:
按照以前这样写;
会什么都不返回,因为传入的address为空,因为没有查到符合条件的,所以没有返回任何东西。
UserDao.java
dart
/**
* 根据address查询用户,如果没有传入地址则查询出所有用户
* @param address
* @return
*/
List<User> findUserListByAddress(@Param("address") String address);
/**
* 根据用户地址和性别查询用户,如果有address才考虑address的条件,如果有sex才考虑sex的条件
* @param user
* @return
*/
List<User> findUserListByAddressAndSex(User user);
UserDao.xml
dart
<select id="findUserListByAddress" parameterType="string" resultType="User">
select * from t_user
<if test="address != null">
where address=#{address}
</if>
</select>
<select id="findUserListByAddressAndSex" parameterType="User" resultType="User">
select * from t_user
where 1=1 //恒成立的一个随便一个条件。为了保证下面任何一个条件符合或不符合,sql语句拼接出来都是正确的。
<if test="address != null">
and address=#{address}
</if>
<if test="sex != null">
and sex=#{sex}
</if>
</select>
测试
dart
@Test
public void test1() {
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.findUserListByAddress(null);
for (User user : userList) {
System.out.println(user);
}
SqlSessionFactoryUtils.commitAndClose(sqlSession);
}
@Test
public void test2() {
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
User user = new User();
user.setAddress("北京");
user.setSex("男");
List<User> userList = mapper.findUserListByAddressAndSex(user);
for (User user1 : userList) {
System.out.println(user1);
}
SqlSessionFactoryUtils.commitAndClose(sqlSession);
}
动态 SQL 之where标签
为了简化上面 where 1=1 的条件拼装,我们可以采用标签来简化开发。
注意:可以自动处理第一个and
UserDao.java
dart
/**
* 根据用户地址和性别查询用户,如果有address才考虑address的条件,如果有sex才考虑sex的条件
* @param user
* @return
*/
List<User> findUserListByAddressAndSex(User user);
UserDao.xml
dart
<select id="findUserListByAddressAndSex" parameterType="User" resultType="User">
select * from t_user
<!--
where标签的作用:
1.可以在添加之前添加where关键字
2.可以去掉第一个条件前的and
-->
<where>
<if test="address != null">
and address=#{address}
</if>
<if test="sex != null">
and sex=#{sex}
</if>
</where>
</select>
测试
dart
@Test
public void test2() {
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
User user = new User();
user.setAddress("北京");
user.setSex("男");
List<User> userList = mapper.findUserListByAddressAndSex(user);
for (User user1 : userList) {
System.out.println(user1);
}
SqlSessionFactoryUtils.commitAndClose(sqlSession);
}
动态标签之foreach标签
需求
• 批量删除: 根据id的集合删除所有元素
以前方式:用in()
但是我们现在无法提前知道是哪些要传的值,所以无法用in()。
UserDao.java
dart
/**
* 批量删除
* @param ids
*/
void deleteByIds(List<Integer> ids);
UserDao.xml:
Foreach标签属性:
collection:要遍历的集合(所以传入方法的是一个集合),
item:遍历出来的元素起个名字
separator:遍历出来的每个元素间的分隔符。
Index:遍历出来的每个元素的索引
Open:遍历出来的第一个元素之前,我们要拼接的字符串。
Close:遍历出来的最后一个元素之后,我们要拼接的字符串。
dart
<delete id="deleteByIds" parameterType="int">//集合的泛型是integer,这里可以用int?
delete from t_user
<foreach collection="list" item="id" separator="," open="where uid in(" close=")">
#{id}
</foreach>
</delete>
测试
dart
@Test
public void test3() {
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<Integer> ids = new ArrayList<Integer>();
ids.add(7);
ids.add(8);
ids.add(9);
mapper.deleteByIds(ids);
SqlSessionFactoryUtils.commitAndClose(sqlSession);
}
动态标签之Sql片段
Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的。我们先到 UserDao.xml 文件中使用标签,定义出公共部分.
UserDao.xml
dart
<sql id="select_all">
select uid,username,sex,birthday,address from t_user
</sql>
<!--
加入一个判断,判断传入的address是否为空,使用if标签进行判断,该标签中的test属性就编写判断条件
-->
<select id="findUserListByAddress" parameterType="string" resultType="User">
<include refid="select_all"></include>
<if test="address != null">
where address=#{address}
</if>
</select>