mybatis学习
一、需要的环境,工具
-
-
Mysql 5.7
-
maven 3.6.1
-
IDEA
SSM框架:有配置文件,最好看官网文档;
二、学习
1、简介
1.1什么是Mybatis
-
MyBatis 是一款优秀的持久层框架,
-
它支持自定义 SQL、存储过程以及高级映射。
-
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
-
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
如何获得Mybatis?
-
maven仓库
-
Github
-
中文文档
1.2持久化
数据持久化
-
持久化就是将程序在持久状态和瞬时状态转化的过程
-
内存:断电即失
-
数据库(JDBC),io文件持久化。
-
生活:冷藏,罐头。
为什么需要持久化?
-
有一些对象,不能让他丢掉
-
内存太贵了
1.3、持久层
Dao层,Service层,Controller层....
-
完成持久化工作的代码块
-
层界限十分明显
1.4、为什么需要Mybatis
-
帮助程序员将数据存入到数据库中。
-
方便
-
传统的JDBC代码太复杂了,简化,框架,自动化。
-
不用Mybatis也可以,只不过使用Mybatis更容易上手,技术没有高低之分。
-
优点:
-
简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
-
灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
-
解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
-
提供映射标签,支持对象与数据库的orm字段关系映射
-
提供对象关系映射标签,支持对象关系组建维护
-
提供xml标签,支持编写动态sql。
-
最重要的一点:使用的人多!
2、第一个Mybatis程序
思路:搭建环境 --> 导入Mybatis --> 编写代码 -->测试
2.1、搭建环境
搭建数据库
1 CREATE DATABASE `mybatis`; 2 3 USE `mybatis`; 4 5 CREATE TABLE `user`( 6 `id` INT(20) NOT NULL PRIMARY KEY, 7 `name` VARCHAR(30) DEFAULT NULL, 8 `pwd` VARCHAR(30) DEFAULT NULL 9 )ENGINE=INNODB DEFAULT CHARSET=utf8; 10 11 INSERT INTO `user`(`id`,`name`,`pwd`) VALUES 12 (1,'张三','123456'), 13 (2,'李四','1223'), 14 (3,'王五','1112')
新建项目
1、新建一个普通的maven项目
2、删除src目录,标记这是一个父工程,在父工程下面建造新的module。
3、导入maven依赖
1 <!--mysql驱动--> 2 <dependecies> 3 <dependency> 4 <groupId>mysql</groupId> 5 <artifactId>mysql-connector-java</artifactId> 6 <version>5.1.46</version> 7 </dependency> 8 9 <!--mybatis--> 10 <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> 11 <dependency> 12 <groupId>org.mybatis</groupId> 13 <artifactId>mybatis</artifactId> 14 <version>3.5.2</version> 15 </dependency> 16 17 <!--junit测试依赖--> 18 <dependency> 19 <groupId>junit</groupId> 20 <artifactId>junit</artifactId> 21 <version>4.12</version> 22 </dependency> 23 24 </dependecies>
2.2、创建一个模块
-
编写mybatis得核心配置文件
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration 3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 5 <!--configuration核心配置文件--> 6 <configuration> 7 <environments default="development"> 8 <environment id="development"> 9 <transactionManager type="JDBC"/> 10 <dataSource type="POOLED"> 11 <property name="driver" value="com.mysql.jdbc.Driver"/> 12 <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/> 13 <property name="username" value="root"/> 14 <property name="password" value="root"/> 15 </dataSource> 16 </environment> 17 </environments> 18 </configuration>
-
编写mybatis工具类
1 //sqlSessionFactory --> sqlSession 2 public class mybatisUtils { 3 4 private static SqlSessionFactory sqlSessionFactory; 5 6 //静态代码块,意味着一开始就进行加载 7 static{ 8 try { 9 //使用mybatis第一步:获取sqlSessionFactory对象 10 String resource="mybatis-config.xml"; 11 InputStream inputStream = Resources.getResourceAsStream(resource); 12 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 13 } catch (IOException e) { 14 e.printStackTrace(); 15 } 16 17 } 18 19 //既然有了sqlSessionFactory,顾名思义,我们就可以从中获得sqlSession实例对象了 20 //sqlSession完全包含了面向数据库执行sql命令所需得所有方法 21 public static SqlSession getSqlSession(){ 22 return sqlSessionFactory.openSession(); 23 } 24 25 }
2.3、编写代码
-
实体类
1 //实体类 2 public class User { 3 private int id; 4 private String name; 5 private String pwd; 6 7 public User() { 8 } 9 10 public User(int id, String name, String pwd) { 11 this.id = id; 12 this.name = name; 13 this.pwd = pwd; 14 } 15 16 public int getId() { 17 return id; 18 } 19 20 public void setId(int id) { 21 this.id = id; 22 } 23 24 public String getName() { 25 return name; 26 } 27 28 public void setName(String name) { 29 this.name = name; 30 } 31 32 public String getPwd() { 33 return pwd; 34 } 35 36 public void setPwd(String pwd) { 37 this.pwd = pwd; 38 } 39 40 @Override 41 public String toString() { 42 return "User{" + 43 "id=" + id + 44 ", name='" + name + '\'' + 45 ", pwd='" + pwd + '\'' + 46 '}'; 47 } 48 }
-
Dao接口类
1 //Dao/Mapper接口类:dao与mybatis中得mapper接口类是一个东西,一个原理 2 public interface UserDao { 3 public List<User> getUserList(); 4 }
-
接口实现类由原来的UserDaoImpl转变为一个Mapper配置文件
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 5 <!--namespace绑定一个Dao/mapper接口类 --> 6 <!--id:对应实现接口中方法的名字;resultType:对应获得结果的实体类--> 7 <!--resultType:代表返回一个结果类,resultMap返回一个集合--> 8 <mapper namespace="com.zmz.Dao.UserDao"> 9 <!--select查询语句--> 10 <select id="getUserList" resultType="com.zmz.pojo.User"> 11 select * from mybatis.user 12 </select> 13 </mapper>
2.4、测试
-
junit测试
1 public class UserMapperTest { 2 3 @Test 4 public void test(){ 5 6 //第一步:获得SqlSession对象 7 SqlSession sqlSession = mybatisUtils.getSqlSession(); 8 9 //第二步:getMapper 10 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 11 List<User> userList = mapper.getUserList(); 12 for (User user: userList){ 13 System.out.println(user); 14 } 15 16 //第三步:关闭sqlSession 17 sqlSession.close(); 18 } 19 20 }
会遇到一个问题:
Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/zmz/Dao/UserMapper.xml
这个问题是由于maven项目所都会遇到的一个问题,就是不能识别resource路径。
maven由于他的约定大于配置,我们之后可能遇到我们写的配置文件,无法被导出或者生效的问题,解决方案:
1 <!--再buil中配置resources,来防止我们资源导出失败的问题--> 2 <build> 3 <resources> 4 <resource> 5 <directory>src/main/resources</directory> 6 <includes> 7 <include>**/*.properties</include> 8 <include>**/*.xml</include> 9 </includes> 10 <filtering>true</filtering> 11 </resource> 12 <resource> 13 <directory>src/main/java</directory> 14 <includes> 15 <include>**/*.properties</include> 16 <include>**/*.xml</include> 17 </includes> 18 <filtering>true</filtering> 19 </resource> 20 </resources> 21 </build>
遇到的问题:
-
配置文件没有注册
-
绑定接口错误
-
方法名不对
-
返回类型不对
-
Maven导出资源问题
3、CRUD
3.1、namespace
namespace中的包名要和Dao/mapper接口的报名一致
3.2、select
选择,查询语句;
-
id:就是对应得namespace中得方法名;
-
resultType:Sql语句执行得返回值
-
parameterType:参数类型
- 编写接口
1 //查询全部用户 2 public List<User> getUserList(); 3 //根据id查询用户 4 public User getUserById(int id);
2.编写对应得mapper中得sql语句
<!--select查询语句--> <select id="getUserList" resultType="com.zmz.pojo.User"> select * from mybatis.user </select> <!--根据id进行查询--> <select id="getUserById" parameterType="int" resultType="com.zmz.pojo.User"> select * from mybatis.user where id = #{id} </select>
3.测试
1 @Test 2 public void test1(){ 3 SqlSession sqlSession = mybatisUtils.getSqlSession(); 4 5 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 6 User userById = mapper.getUserById(1); 7 System.out.println(userById); 8 sqlSession.close(); 9 }
3.3、Insert
1 //insert一个用户 2 public int insertUser(User user); 3 <insert id="insertUser" parameterType="com.zmz.pojo.User"> 4 insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd}); 5 </insert> 6 //增删改需要提交事务 7 @Test 8 public void test2(){ 9 SqlSession sqlSession = mybatisUtils.getSqlSession(); 10 11 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 12 13 int i = mapper.insertUser(new User(4, "zmz", "11")); 14 if (i > 0){ 15 System.out.println("插入成功!"); 16 } 17 //提交事务 18 sqlSession.commit(); 19 sqlSession.close(); 20 }
3.4、Update
1 //update一个用户 2 public int updateUser(User user); 3 <update id="updateUser" parameterType="com.zmz.pojo.User"> 4 update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}; 5 </update> 6 @Test 7 public void test3(){ 8 SqlSession sqlSession = mybatisUtils.getSqlSession(); 9 10 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 11 int i = mapper.updateUser(new User(4, "z", "113")); 12 if (i>0){ 13 System.out.println("修改成功"); 14 } 15 sqlSession.commit(); 16 sqlSession.close(); 17 }
3.5、Delete
1 //delete一个用户 2 public int deleteUser(int id); 3 <delete id="deleteUser" parameterType="int"> 4 delete from mybatis.user where id = #{id}; 5 </delete> 6 @Test 7 public void test4(){ 8 SqlSession sqlSession = mybatisUtils.getSqlSession(); 9 10 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 11 int i = mapper.deleteUser(4); 12 if (i > 0 ){ 13 System.out.println("删除成功!"); 14 } 15 sqlSession.commit(); 16 sqlSession.close(); 17 }
注意:增删改需要提交事务
3.6、万能Map
使用map之后可以将你想修改或者插入的值进行map.put(key,value)
key值对应你要修改的值。
假设,我们的实体类,或者数据库中的表,字段或者参数过多,我们应当考虑使用Map!
1 //万能得Map 2 public int insertUser2(Map<String, Object> map); 3 <insert id="insertUser2" parameterType="map"> 4 insert into mybatis.user (id, name, pwd) values (#{userId}, #{userName}, #{UserPwd}); 5 </insert> 6 @Test 7 public void insertUser2(){ 8 SqlSession sqlSession = mybatisUtils.getSqlSession(); 9 10 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 11 12 Map<String,Object> map = new HashMap<String,Object>(); 13 map.put("userId",5); 14 map.put("userName","zzz"); 15 map.put("UserPwd","1123"); 16 mapper.insertUser2(map); 17 18 sqlSession.commit(); 19 sqlSession.close(); 20 }
Map传递参数,直接在sql中取出key即可!【parameterType="map"】
对象传递参数,直接在sql中取出对象的属性即可!【parameterType="Object"】
只有一个基本类型参数的情况下,可以直接在sql中取到!
多个参数用Map,或者注解!
3.7模糊查询(某些业务才是用到)
模糊查询怎么写?
-
java代码执行的时候,传递通配符%%
1 <select id="getUserListLike" resultType="com.zmz.pojo.User"> 2 select * from user where name like #{value}; 3 </select> 4 @Test 5 public void getUserListLike(){ 6 SqlSession sqlSession = mybatisUtils.getSqlSession(); 7 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 8 List<User> userListLike = mapper.getUserListLike("%李%"); 9 for (User user: userListLike){ 10 System.out.println(user); 11 } 12 sqlSession.close(); 13 }
-
在sql拼接中使用通配符!
1 <select id="getUserListLike" resultType="com.zmz.pojo.User"> 2 select * from user where name like "%"#{value}"%"; 3 </select>
1 @Test 2 public void getUserListLike(){ 3 SqlSession sqlSession = mybatisUtils.getSqlSession(); 4 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 5 List<User> userListLike = mapper.getUserListLike("李"); 6 for (User user: userListLike){ 7 System.out.println(user); 8 } 9 sqlSession.close(); 10 } 11 12 }
4、配置解析
4.1、核心配置文件
-
mybatis-config.xml
-
MyBatis的配置文件包含了会深深影响MyBatis行为的设置和属性信息
configuration(配置): properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置): environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器)
4.2、环境配置(environments)
MyBatis可以配置成适应多种环境
不过要记住,尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择一种环境。
学会使用配置多套运行环境!
Mybatis默认的事务管理器:JDBC 连接池:POOLED
4.3、属性(properties)
我们可以通过properties属性来实现引用配置文件
这些属性都是可外部配置且动态替换的,既可以在典型的java属性文件中配置,亦可通过properties元素的子元素来传递。[db.properties]
编写一个配置文件
db.properties
1 driver=com.mysql.jdbc.Driver 2 url=jdbc:mysql://localhost:/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8 3 username=root 4 password=root
在核心配置文件中引入
1 <!-- 必须按照顺序进行放置属性标签,否则会报错,引入外部配置文件--> 2 <properties resource="db.properties"> 3 <property name="username" value="root"/> 4 <property name="password" value="root"/> 5 </properties> 6 7 <environments default="development"> 8 <environment id="development"> 9 <transactionManager type="JDBC"/> 10 <dataSource type="POOLED"> 11 <property name="driver" value="${driver}"/> 12 <property name="url" value="${url}"/> 13 <property name="username" value="${username}"/> 14 <property name="password" value="${password}"/> 15 </dataSource>
-
可以直接引入外部文件
-
可以在其中增加一些属性配置
-
如果两个文件有同一个字段,优先使用外部配置文件的(就是db.properties文件)!
4.4、类型别名(typeAliases)
-
类型别名可为 Java 类型设置一个缩写名字。
-
意在降低冗余的全限定类名书写。
1 <typeAliases> 2 <typeAlias type="com.zmz.pojo.User" alias="User"/> 3 </typeAliases>
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,
扫描实体类的包,它的默认别名就为这个类的类名,首字母大写(默认是这样,但不大写也能扫描出来)
1 <!--可以给实体类起别名--> 2 <typeAliases> 3 <package name="com.zmz.pojo"/> 4 </typeAliases>
-
在实体类比较少的时候,使用第一种方式;
-
如果实体类十分多,建议使用第二种;
-
第一种可以DIY别名,第二种”不行”,如果非要改,需要在实体上增加注解。
@Alias("user") public class User { ... }
4.5、设置
这是MyBatis中极为重要的调整设置,他们会改变MyBatis的运行时行为。
1 <settings> 2 <setting name="cacheEnabled" value="true"/> 3 <setting name="lazyLoadingEnabled" value="true"/> 4 <setting name="useGeneratedKeys" value="false"/> 5 <setting name="mapUnderscoreToCamelCase" value="false"/> 6 </settings>
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 |
true | false | false |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 | true | false | False |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
比较重要的几个,放在这;具体的请看:https://mybatis.org/mybatis-3/zh/configuration.html
4.6、其他配置
-
[plugins(插件)]
-
mybatis-generator-core
-
mybatis-plus 学习网站:https://baomidou.com/
-
通用mapper
-
初学不涉及这方面;
4.7、映射器(mappers)
MapperRegistry:注册绑定我们的Mapper文件;
方式一:【推荐使用】
1 <!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册--> 2 <mappers> 3 <mapper resource="com/zmz/Dao/UserMapper.xml"/> 4 </mappers>
方式二:使用class文件绑定注册
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册--> <mappers> <mapper class="com.zmz.Dao.UserMapper"/> </mappers>
注意:
接口和他的Mapper配置文件必须同名!
接口和它的Mapper文件必须在同一个包下!
方式三:使用扫描包进行注入绑定
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册--> <mappers> <package name="com.zmz.Dao"/> </mappers>
注意:
接口和他的Mapper配置文件必须同名!
接口和它的Mapper文件必须在同一个包下!
4.8、生命周期和作用域
生命周期和作用域,是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder:
-
一旦创建了SqlSessionFactory,就不再需要它了
-
局部变量
SqlSessionFactory:
-
说白了就是可以想象为:数据库连接池
-
SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
-
因此SqlSessionFactory的最佳作用域是应用作用域。
-
最简单的就是使用单例模式或者静态单例模式。
SqlSession:
-
连接到连接池的一个请求!
-
SqlSession的实例不是线程安全的,因此是不能被共享的,所以他的最佳作用时间域是请求或方法作用域。
-
用完之后需要赶紧关闭,否则资源被占用!
这里面的每一个Mapper,就代表一个具体的业务!(可以这么理解)
5、解决属性名和字段名不一致的问题
数据库中表的属性名和类的字段不一样,
数据库user表
id name pwd
对应类的字段:
1 public class User { 2 private int id; 3 private String name; 4 private String password; 5 }
解决方法:
1.起别名
1 <select id="getUserById" parameterType="int" resultType="User"> 2 select id,name,pwd as password from mybatis.user where id = #{id} 3 </select>
2.ResultMap映射
1 <!--结果集映射--> 2 <resultMap id="UserMap" type="User"> 3 <!--column:代表数据库表中属性,property:代表类中字段--> 4 <result column="id" property="id"/> 5 <result column="name" property="name"/> 6 <result column="pwd" property="password"/> 7 </resultMap> 8 9 <select id="getUserById" parameterType="int" resultMap="UserMap"> 10 select * from user where id = #{id} 11 </select>
-
resultMap
元素是 MyBatis 中最重要最强大的元素。 -
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
-
ResultMap
的优秀之处——你完全可以不用显式地配置它们。
当你的属性为一个另一个类时,这就牵扯到了一对多,和多对多的问题了。
<association ...> 代表关联关系,多个学生关联一个老师
<collection ...> 代表集合关系, 一个老师包含多个学生
6、日志
6.1、日志工厂
如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手!
曾经:sout,debug
现在:日志工厂!
logImpl :指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
-
SLF4J
-
LOG4J (*)
-
LOG4J2
-
JDK_LOGGING
-
COMMONS_LOGGING
-
STDOUT_LOGGING (*)
-
NO_LOGGING
在Mybatis中具体使用哪一个日志实现,在设置中设定!
STDOUT_LOGGING标准日志输出
在mybatis核心配置文件中,配置我们的日志!
1 <!--标准,每一个包括大小写和空格,不能有错误,标准的日志工程实现--> 2 <settings> 3 <setting name="logImpl" value="STDOUT_LOGGING"/> 4 </settings>
6.2、LOG4J
简介:
-
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件;
-
我们也可以控制每一条日志的输出格式;
-
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程;
-
以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
-
先导入LOG4J的包,
1 <!-- https://mvnrepository.com/artifact/log4j/log4j --> 2 <dependency> 3 <groupId>log4j</groupId> 4 <artifactId>log4j</artifactId> 5 <version>1.2.17</version> 6 </dependency>
-
log4j两种配置文件:
-
log4j.properties
# priority :debug<info<warn<error #you cannot specify every priority with different file for log4j log4j.rootLogger=debug,stdout,info,debug,warn,error #console log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n #info log log4j.logger.info=info log4j.appender.info=org.apache.log4j.DailyRollingFileAppender log4j.appender.info.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.info.File=./src/com/hp/log/info.log log4j.appender.info.Append=true log4j.appender.info.Threshold=INFO log4j.appender.info.layout=org.apache.log4j.PatternLayout log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n #debug log log4j.logger.debug=debug log4j.appender.debug=org.apache.log4j.DailyRollingFileAppender log4j.appender.debug.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.debug.File=./src/com/hp/log/debug.log log4j.appender.debug.Append=true log4j.appender.debug.Threshold=DEBUG log4j.appender.debug.layout=org.apache.log4j.PatternLayout log4j.appender.debug.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n #warn log log4j.logger.warn=warn log4j.appender.warn=org.apache.log4j.DailyRollingFileAppender log4j.appender.warn.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.warn.File=./src/com/hp/log/warn.log log4j.appender.warn.Append=true log4j.appender.warn.Threshold=WARN log4j.appender.warn.layout=org.apache.log4j.PatternLayout log4j.appender.warn.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n #error log4j.logger.error=error log4j.appender.error = org.apache.log4j.DailyRollingFileAppender log4j.appender.error.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.error.File = ./src/com/hp/log/error.log log4j.appender.error.Append = true log4j.appender.error.Threshold = ERROR log4j.appender.error.layout = org.apache.log4j.PatternLayout log4j.appender.error.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n log4j.xml
-
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration debug="true" xmlns:log4j='http://jakarta.apache.org/log4j/' > <!-- ========================== 自定义输出格式说明================================ --> <!-- %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL --> <!-- %r 输出自应用启动到输出该log信息耗费的毫秒数 --> <!-- %c 输出所属的类目,通常就是所在类的全名 --> <!-- %t 输出产生该日志事件的线程名 --> <!-- %n 输出一个回车换行符,Windows平台为“/r/n”,Unix平台为“/n” --> <!-- %d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921 --> <!-- %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlo4.main(TestLog4.java:10) --> <!-- ========================================================================== --> <!-- ========================== 输出方式说明================================ --> <!-- Log4j提供的appender有以下几种: --> <!-- org.apache.log4j.ConsoleAppender(控制台), --> <!-- org.apache.log4j.FileAppender(文件), --> <!-- org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件), --> <!-- org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件), --> <!-- org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方) --> <!-- ========================================================================== --> <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender"> <!-- <param name="Target" value="System.out"/> --> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c Method: %l ]%n%p:%m%n"/> </layout> <!-- <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="LevelMin" value="DEBUG"/> <param name="LevelMax" value="DEBUG"/> </filter> --> </appender> <!-- output the debug --> <!-- <appender name="log4jDebug" class="org.apache.log4j.DailyRollingFileAppender"> <param name="File" value="log_"/> <param name="MaxFileSize" value="KB"/> <param name="MaxBackupIndex" value="2"/> --> <appender name="log4jDebug" class="org.apache.log4j.rolling.RollingFileAppender"> <param name="Append" value="true"/> <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy"> <param name="FileNamePattern" value="./log/log_%d{yyyy-MM-dd}.log" /> </rollingPolicy> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c Method: %l ]%n%p:%m%n"/> </layout> <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="LevelMin" value="DEBUG"/> <param name="LevelMax" value="DEBUG"/> </filter> </appender> <!-- <appender name="log4jInfo" class="org.apache.log4j.DailyRollingFileAppender"> <param name="File" value="log_"/> <param name="DatePattern" value="'.log'yyyy-MM-dd"/> <param name="Append" value="true"/> <param name="MaxFileSize" value="5KB"/> <param name="MaxBackupIndex" value="2"/> --> <appender name="log4jInfo" class="org.apache.log4j.rolling.RollingFileAppender"> <param name="Append" value="true"/> <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy"> <param name="FileNamePattern" value="./log/log_%d{yyyy-MM-dd}.log" /> </rollingPolicy> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c Method: %l ]%n%p:%m%n"/> </layout> <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="LevelMin" value="INFO"/> <param name="LevelMax" value="INFO"/> </filter> </appender> <!-- <appender name="log4jWarn" class="org.apache.log4j.DailyRollingFileAppender"> <param name="File" value="/log_"/> <param name="DatePattern" value="'.log'yyyy-MM-dd"/> <param name="Append" value="true"/> <param name="MaxFileSize" value="5KB"/> <param name="MaxBackupIndex" value="2"/> --> <appender name="log4jWarn" class="org.apache.log4j.rolling.RollingFileAppender"> <param name="Append" value="true"/> <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy"> <param name="FileNamePattern" value="./log/log_%d{yyyy-MM-dd}.log" /> </rollingPolicy> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c Method: %l ]%n%p:%m%n"/> </layout> <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="LevelMin" value="WARN"/> <param name="LevelMax" value="WARN"/> </filter> </appender> <!-- <appender name="log4jError" class="org.apache.log4j.DailyRollingFileAppender"> --> <appender name="log4jError" class="org.apache.log4j.rolling.RollingFileAppender"> <!-- <param name="File" value="/error_"/> <param name="DatePattern" value="'.log'yyyy-MM-dd"/> --> <param name="Append" value="true"/> <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy"> <param name="FileNamePattern" value="./log/error_%d{yyyy-MM-dd}.log" /> </rollingPolicy> <!-- <param name="MaxFileSize" value="5KB"/> --> <!-- <param name="MaxBackupIndex" value="2"/> --> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c Method: %l ]%n%p:%m%n"/> </layout> <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="LevelMin" value="ERROR"/> <param name="LevelMax" value="ERROR"/> </filter> </appender> <!--通过<category></category>的定义可以将各个包中的类日志输出到不同的日志文件中--> <!-- <category name="com.gzy"> <priority value="debug" /> <appender-ref ref="log4jTestLogInfo" /> <appender-ref ref="log4jTestDebug" /> </category> --> <appender name="MAIL" class="org.apache.log4j.net.SMTPAppender"> <param name="threshold" value="debug" /> <!-- 日志的错误级别 <param name="threshold" value="error"/> --> <!-- 缓存文件大小,日志达到512K时发送Email --> <param name="BufferSize" value="512" /><!-- 单位K --> <param name="From" value="test@163.com" /> <param name="SMTPHost" value="smtp.163.com" /> <param name="Subject" value="juyee-log4jMessage" /> <param name="To" value="test@163.com" /> <param name="SMTPUsername" value="test" /> <param name="SMTPPassword" value="test" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss.SSS a} [%p]-[%c] %m%n" /> </layout> </appender> <root> <priority value="debug"/> <appender-ref ref="CONSOLE" /> <appender-ref ref="log4jDebug" /> <appender-ref ref="log4jInfo" /> <appender-ref ref="log4jWarn" /> <appender-ref ref="log4jError" /> <!-- <appender-ref ref="MAIL" /> --> </root> </log4j:configuration>
-
-
配置log4j为日志的实现
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
-
Log4j的使用!直接测试运行刚才的查询
简单使用
-
在要使用Log4j的类中,导入包import org.apache.Logger;
-
日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(UserMapperTest.class);
-
日志级别
logger.info("info:进入了log4j"); logger.debug("debug:进入了log4j"); logger.error("error:进入了log4j");
总结:日志就是一个对于你运行程序过程中的一个输出,包括各种info,debug,error等的输出类别分类输出,他取代了最早之前的System.out.println()的输出方式,使得成为一个体系,同时,logger.debug("进入测试")可以使得日志工厂变得灵活,多变,同时增加配置文件更加方便你进行输出格式的变换。
7、分页
思考:为什么要分页
-
减少数据的处理量
7.1使用Limit分页
语法:SELECT * from user limit startIndex, pageSize; (从第几个数开始,然后输出多少个数,这个就叫分页) SELECT * from user limit 3; #[0, n](只有一个参数时,默认从0开始算起)
使用Mybatis实现分页,核心SQL
-
接口
1 //根据分页查询用户 2 public List<User> getUserByLimit(Map<String, Integer> map);
-
Mapper.xml
1 <!-- 分页 --> 2 <select id="getUserByLimit" parameterType="map" resultMap="UserMap"> 3 select * from user limit #{startIndex}, #{pageSize} 4 </select>
-
测试
1 @Test 2 public void testLimit(){ 3 SqlSession sqlSession = mybatisUtils.getSqlSession(); 4 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 5 HashMap<String, Integer> map = new HashMap<String, Integer>(); 6 map.put("startIndex", 0); 7 map.put("pageSize",2); 8 9 List<User> userList = mapper.getUserByLimit(map); 10 for (User user: userList){ 11 System.out.println(user); 12 } 13 14 sqlSession.close(); 15 }
7.2RowBounds分页
不再使用SQL实现分页
-
接口
1 //分页2 2 public List<User> getUserByRowBounds();
-
mapper.xml
1 <!-- 分页2 --> 2 <select id="getUserByRowBounds" resultMap="UserMap"> 3 select * from user 4 </select>
-
测试
1 @Test 2 public void getUserByRowBounds(){ 3 SqlSession sqlSession = mybatisUtils.getSqlSession(); 4 // UserMapper mapper = sqlSession.getMapper(UserMapper.class); 5 6 RowBounds rowBounds = new RowBounds(1,2); 7 8 //通过java代码层面进行实现分页 9 List<User> userlist = sqlSession.selectList("com.zmz.Dao.UserMapper.getUserByRowBounds",null,rowBounds); 10 11 for (User user : userlist) { 12 System.out.println(user); 13 } 14 15 sqlSession.close(); 16 }
-
7.3、分页插件
mybatis-PageHelper分页插件使用
了解即可,知道怎么使用即可,网上有教程使用。
8、使用注解开发
8.1、面向接口编程
-
面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法。
-
面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现。
-
接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构。
abstract class 针对某一个个体的抽象
interface针对某一个个体的某一方面的抽象编写。
8.2、使用注解开发
-
注解在接口上实现
1 @Select("select * from user") 2 public List<User> getUsers();
-
需要在核心配置文件中绑定接口!
1 <mappers> 2 <mapper class="com.zmz.Dao.UserMapper"/> 3 </mappers>
-
测试
1 @Test 2 public void test01(){ 3 SqlSession sqlSession = mybatisUtils.getSqlSession(); 4 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 5 6 List<User> users = mapper.getUsers(); 7 for (User user : users) { 8 System.out.println(user); 9 } 10 11 sqlSession.close(); 12 13 }
本质:反射机制实现
底层:动态代理!
总结:注解方式只能试用那些简单的sql语句,如果太过复杂,比如一些别名比较多,比较复杂的sql语句,还是需要xml方式进行名称映射。
Mybatis详细的执行流程!
8.3、CRUD
我们可以在工具类创建的时候实现自动提交事务!
1 public static SqlSession getSqlSession(){ 2 return sqlSessionFactory.openSession(true); 3 //是一个重构的函数,加上参数ture,意味着事务自动提交,不需要在手动提交事务了 4 }
编写接口,增加注解
1 //使用注解方式进行使用mybatis 2 //实体类:pojo --》 entity --》 domain 3 4 public interface UserMapper { 5 6 @Select("select * from user") 7 public List<User> getUsers(); 8 9 //方法存在多个参数,所有的参数前面必须加上@Param("id")注解 10 //里面参数的值随便取名称,但是注解中的名称必须要与#{id}相对应 11 @Select("select * from user where id=#{id}") 12 public User getUserById(@Param("id") int id2); 13 14 @Insert("insert into user(id, name, pwd) values (#{id}, #{name}, #{password})") 15 public int addUser(User user); 16 17 @Update("update user set name=#{name}, pwd=#{password} where id=#{id}") 18 public int updateUser(User user); 19 20 @Delete("delete from user where id=#{uid}") 21 public int deleteUser(@Param("uid") int id); 22 23 }
测试类
1 @Test 2 public void test01(){ 3 SqlSession sqlSession = mybatisUtils.getSqlSession(); 4 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 5 6 // List<User> users = mapper.getUsers(); 7 // for (User user : users) { 8 // System.out.println(user); 9 // } 10 // User userById = mapper.getUserById(1); 11 // System.out.println(userById); 12 13 // mapper.addUser(new User(7, "hello", "123122")); 14 15 // mapper.updateUser(new User(7,"to","122112")); 16 17 mapper.deleteUser(7); 18 19 sqlSession.close(); 20 21 }
【注意】:我们必须要将接口注册绑定到我们的核心配置文件中!
关于@Param()注解
-
基本类型的参数或者String类型,需要加上
-
引用类型不需要加
-
如果只有一个基本类型的话,可以忽略,但是建议大家都加上
-
我们在SQL中引用的就是我们这里的@Param()中设定的属性名!
#{} , ${}区别
网上的答案是:#{}是预编译处理,$ {}是字符串替换。mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;mybatis在处理 $ { } 时,就是把 ${ } 替换成变量的值。使用 #{} 可以有效的防止SQL注入,提高系统安全性。
对于这个题目我感觉要抓住两点: (1)$ 符号一般用来当作占位符,常使用Linux脚本的人应该对此有更深的体会吧。既然是占位符,当然就是被用来替换的。知道了这点就能很容易区分$和#,从而不容易记错了。 (2)预编译的机制。预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。
总结:多用#{}(安全),少用或不用${}(不安全)
9、Lombok
简介
-
Lombok 是一种 Java™ 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注解实现这一目的。
-
要使用这个对象,必须还要写一些getter和setter方法,可能还要写一个构造器、equals方法、或者hash方法.这些方法很冗长而且没有技术含量,我们叫它样板式代码. lombok的主要作用是通过一些注解,消除样板式代码,像这样:
1 @Data 2 public class Mountain{ 3 private String name; 4 private double longitude; 5 private String country; 6 }
-
如果觉得@Data这个注解有点简单粗暴的话,Lombok提供一些更精细的注解,比如@Getter,@Setter,(这两个是field注解),@ToString,@AllArgsConstructor(这两个是类注解). 这些可能是最常见的用法,更详细的用法可以看Lombok feature overview这里.
使用步骤:
-
在IDEA中安装Lombok插件!
-
在项目中导入lombok的jar包!
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency>
-
在实体类上加注解即可!
@Data: 无参构造,get,set, toString,hashcode,equals @AllArgsConstructor @NoArgsConstructor @Getter and @Setter:可以放在类上,也可以放在字段上单个建立这个方法 @ToString
-
说明方法及使用
@Getter and @Setter @FieldNameConstants @ToString @EqualsAndHashCode @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog @Data @Builder @SuperBuilder @Singular @Delegate @Value @Accessors @Wither @With @SneakyThrows @val @var experimental @var @UtilityClass Lombok config system
说明:
@Data: 无参构造,get,set, toString,hashcode,equals @AllArgsConstructor @NoArgsConstructor @Getter and @Setter:可以放在类上,也可以放在字段上单个建立这个方法 @ToString 基本常用的几个,有参,无参构造函数
优点是使用注解即可帮忙自动生成代码,大大减少了代码量,使代码非常简洁。
但是并不意味着Lombok的使用没有任何问题,在使用Lombok的过程中,还可能存在对队友不友好、对代码不友好、对调试不友好、对升级不友好等问题。
最重要的是,使用Lombok还会导致破坏封装性的问题。
虽然使用Lombok存在着很多方便,但是也带来了一些问题。
总结:尽量少用,他减少了代码可读性和舒适性,有一些插件减免了我们一些工作,但也要知道,适合最重要,而不是一味追求高大上技术。
10、多对一处理
-
多对一:多个学生,对应一个老师
-
对于学生这边而言,关联...多个学生,关联一个老师【多对一】
-
对于老师而言,集合..一个老师有很多学生,【一对多】
建立两个多对一的实体类表(SQL语句):
1 CREATE TABLE `teacher`( 2 `id` INT(10) NOT NULL, 3 `name` VARCHAR(30) DEFAULT NULL, 4 PRIMARY KEY (`id`) 5 )ENGINE=INNODB DEFAULT CHARSET=utf8 6 7 INSERT INTO teacher(`id`,`name`) VALUES (1,'庆老师'); 8 9 CREATE TABLE `student`( 10 `id` INT(10) NOT NULL, 11 `name` VARCHAR(30) DEFAULT NULL, 12 `tid` INT(10) DEFAULT NULL, 13 PRIMARY KEY(`id`), 14 KEY `fktid` (`tid`), 15 CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`) 16 )ENGINE=INNODB DEFAULT CHARSET=utf8 17 18 INSERT into `student`(`id`,`name`,`tid`) VALUES ('1','小明','1'); 19 INSERT into `student`(`id`,`name`,`tid`) VALUES ('2','小红','1'); 20 INSERT into `student`(`id`,`name`,`tid`) VALUES ('3','小张','1'); 21 INSERT into `student`(`id`,`name`,`tid`) VALUES ('4','小王','1'); 22 INSERT into `student`(`id`,`name`,`tid`) VALUES ('5','小李','1');
测试环境搭建
-
导入lombok
-
新建实体类Teacher, Student
-
建立Mapper接口
-
建立Mapper.xml文件
-
在核心配置文件中绑定注册我们的Mapper接口或者文件!
-
测试查询是否能够成功!
按照查询嵌套处理
1 <!-- 2 思路: 3 1、查询所有的学生信息 4 2、根据查询出来的学生的tid,寻找对应的老师! 子查询 5 --> 6 7 <resultMap id="StudentTeacher" type="Student"> 8 <result property="id" column="id"/> 9 <result property="name" column="name"/> 10 <!--复杂的属性,我们需要单独处理--> 11 <!-- 12 对象:association 13 集合:collection 14 --> 15 <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> 16 </resultMap> 17 18 <select id="getStudent" resultMap="StudentTeacher"> 19 select * from student 20 </select> 21 <select id="getTeacher" resultType="Teacher"> 22 select * from teacher where id=#{id} 23 </select>
按照结果嵌套处理
1 <!--按照结果嵌套处理--> 2 <select id="getStudent2" resultMap="StudentTeacher2"> 3 select s.id sid, s.name sname, t.name tname 4 from student s, teacher t 5 where s.tid = t.id 6 </select> 7 8 <resultMap id="StudentTeacher2" type="Student"> 9 <result property="id" column="sid"/> 10 <result property="name" column="sname"/> 11 <association property="teacher" javaType="Teacher"> 12 <result property="name" column="tname"/> 13 </association> 14 </resultMap>
回顾Mysql多对一查询方式
-
子查询
-
联表查询
11、一对多处理
例:一个老师拥有多个学生
对于老师来说,就是一对多关系
搭建环境
与上面一样
按照结果嵌套处理(推荐这个)
1 <!--按结果嵌套查询--> 2 <resultMap id="TeacherStudent" type="Teacher"> 3 <result property="id" column="tid"/> 4 <result property="name" column="tname"/> 5 <!--复杂的属性,我们需要单独处理--> 6 <!-- 7 对象:association 8 集合:collection 9 --> 10 <collection property="students" ofType="Student"> 11 <result property="id" column="sid"/> 12 <result property="name" column="sname"/> 13 <result property="tid" column="tid"/> 14 </collection> 15 </resultMap> 16 17 <select id="getTeacher" resultMap="TeacherStudent"> 18 select s.id sid, s.name sname, t.id tid, t.name tname 19 from student s, teacher t 20 where t.id=#{tid} and t.id = s.tid 21 </select>
按照查询嵌套处理
1 <resultMap id="TeacherStudent2" type="Teacher"> 2 <result property="id" column="id"/> 3 <result property="name" column="name"/> 4 <collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudent"/> 5 </resultMap> 6 <select id="getTeacher2" resultMap="TeacherStudent2"> 7 select * from teacher where id =#{tid} 8 </select> 9 <select id="getStudent" resultType="Student"> 10 select * from student where tid = #{tid} 11 </select>
小结:
-
关联 - association [多对一]
-
集合 - collection [一对多]
-
javaType & ofType
-
javaType 用来指定实体类中属性的类型
-
ofType 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型!
-
注意
-
保证SQL的可读性,尽量保证通俗易懂
-
注意一对多和多对一中;属性名和字段的问题!
-
如果问题不好排查错误,可以使用日志,建议使用Log4j
sql面试高频
-
MySQL引擎
-
INNODB底层原理
-
索引
-
索引优化!
12、动态SQL
简介
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。 使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。 动态SQL就是根据不同的条件生成不同的SQL语句
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。 if choose (when, otherwise) trim (where, set) foreach
搭建环境
1 CREATE TABLE `blog`( 2 `id` VARCHAR(50) NOT NULL COMMENT '博客id', 3 `title` VARCHAR(100) NOT NULL COMMENT '博客标题', 4 `author` VARCHAR(30) NOT NULL COMMENT '博客作者', 5 `create_time` datetime NOT NULL COMMENT '创建时间', 6 `views` INT(30) NOT NULL COMMENT '浏览量' 7 )ENGINE=INNODB DEFAULT CHARSET=utf8
创建一个基础工程
-
导包
-
编写配置文件
-
编写实体类
1 @Data 2 public class Blog { 3 private String id; 4 private String title; 5 private String author; 6 private Date createTime; //属性名和数据库字段名不一致 7 private int views; 8 }
-
编写实体类对应Mapper接口及Mapper.xml文件
IF
1 <select id="queryBlogIF" parameterType="map" resultType="Blog"> 2 select * from blog where 1=1 3 <if test="author !=null"> 4 and author =#{author} 5 </if> 6 <if test="title !=null"> 7 and title=#{title} 8 </if> 9 </select>
choose (when, otherwise)
1 <select id="queryBlogChoose" parameterType="map" resultType="Blog"> 2 select * from blog 3 <where> 4 <choose> 5 <when test="author !=null"> 6 author =#{author} 7 </when> 8 <when test="title != null"> 9 title = #{title} 10 </when> 11 <otherwise> 12 views = #{views} 13 </otherwise> 14 </choose> 15 </where> 16 </select>
choose标签类似switch..case语句;
trim(where,set)
1 <select id="queryBlogIF" parameterType="map" resultType="Blog"> 2 select * from blog 3 <where> 4 <if test="author !=null"> 5 author =#{author} 6 </if> 7 <if test="title !=null"> 8 and title=#{title} 9 </if> 10 </where> 11 </select>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
1 <update id="updateBlog" parameterType="map"> 2 update blog 3 <set> 4 <if test="author != null"> 5 author = #{author}, 6 </if> 7 <if test="title != null"> 8 title = #{title} 9 </if> 10 </set> 11 where id =#{id} 12 </update>
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
所谓的动态SQL,本质还是SQL语句,只是我们可以再SQL层面,去执行一个逻辑代码.
Foreach
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
1 <select id="queryBlogForeach" parameterType="map" resultType="Blog"> 2 select * from blog 3 <where> 4 <foreach collection="ids" item="id" open="and (" separator="or" close=")"> 5 id=#{id} 6 </foreach> 7 </where> 8 </select>
总结:将你想遍历的list进行一个一个遍历,之间还可以进行拼接,将其拼接成不同样式。
测试类:
1 @Test 2 public void test03(){ 3 SqlSession sqlSession = mybatisUtils.getSqlSession(); 4 BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); 5 6 7 8 ArrayList<Integer> ids = new ArrayList<Integer>(); 9 ids.add(1); 10 ids.add(2); 11 ids.add(3); 12 13 HashMap map = new HashMap(); 14 map.put("ids",ids); 15 16 List<Blog> blogs = mapper.queryBlogForeach(map); 17 for (Blog blog : blogs) { 18 System.out.println(blog); 19 } 20 sqlSession.close(); 21 }
SQL片段
有的时候,我们可能会讲一些功能的部分抽取出来,方便复用!
-
使用SQL标签抽取公共的部分
1 <!--将你经常或多次要复用的代码放入sql标签中,然后在其他标签中使用include标签进行调用即可--> 2 <sql id="queryBlogIF"> 3 <if test="author !=null"> 4 and author =#{author} 5 </if> 6 <if test="title !=null"> 7 and title=#{title} 8 </if> 9 </sql>
-
在需要使用的地方使用include标签引用即可
1 <select id="queryBlogIF" parameterType="map" resultType="Blog"> 2 select * from blog 3 <where> 4 <include refid="queryBlogIF"/> 5 </where> 6 </select>
注意事项:
-
最好基于单表来定义SQL片段!
-
不要存在where标签
总结:动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了。所以,建议在mysql中写出完整的SQL,再对应的去修改成为我们的动态sql实现通用即可!
13、缓存
数据读取架构
13.1、简介
-
什么是缓存[Cache]?
-
存在内存中的临时数据。
-
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
-
-
为什么使用缓存?
-
减少和数据的交互次数,减少系统开销,提高系统效率。
-
-
什么样的数据能使用缓存?
-
经常查询并且不经常改变的数据。
-
13.2、Mybatis缓存
-
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
-
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
-
默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也成为本地缓存)
-
二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
-
为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
-
13.3、一级缓存
测试步骤:
-
开启日志!
-
测试在一个Session中查询两次相同记录
-
查询过程中进行比较,发现查询相同id的用户,在一次sqlSession连接中,是相同的,所以第二次的查询的地方是在缓存中进行查询。
-
-
查看日志输出
-
输出为true,对于两次结果比较得出,查询的地址都相同。
-
分为两次sqlSession进行查询的结果进行比较为false。
-
缓存失效的情况:
-
查询不同的东西
-
增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
-
查询不同的Mapper.xml
-
手动清理缓存!
-
缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
-
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!
13.4、二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
-
基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
-
工作机制
-
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存;
-
如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
-
新的会话查询信息,就可以从二级缓存中获取内容;
-
不同的mapper查出的数据会放在自己对应的缓存(map)中;
-
步骤:
-
开启全局缓存
1 <!--显示的开启全局缓存--> 2 <setting name="cacheEnabled" value="true"/>
-
在要使用的二级缓存的Mapper中开启
1 <!--在当前Mapper.xml中使用二级缓存--> 2 <cache/>
也可以自定义参数
1 <cache 2 eviction="FIFO" 3 flushInterval="60000" 4 size="512" 5 readOnly="true"/>
-
测试
-
问题:我们需要将实体类序列化,否则就会报错
Cause: java.io.NotSerializableException: com.zmz.pojo.User
-
小结:
-
只要开启了二级缓存,在同一个Mapper下就有效
-
所有的数据都会先放在一级缓存中;
-
只有当会话提交,或者关闭的时候,才会提交到二级缓存中!
13.5、缓存原理
缓存原理图:
缓存顺序:
-
先看二级缓存中有没有
-
再看一级缓存中有没有
-
查询数据库
13.6、自定义缓存-ehcache
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点;
要在程序中使用ehcache,先要导包!
1 <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> 2 <dependency> 3 <groupId>org.mybatis.caches</groupId> 4 <artifactId>mybatis-ehcache</artifactId> 5 <version>1.2.1</version> 6 </dependency>
在mapper中指定使用我们的ehcache缓存实现!
1 <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
ehcache.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" 4 updateCheck="false"> 5 <!-- 6 diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下: 7 user.home – 用户主目录 8 user.dir – 用户当前工作目录 9 java.io.tmpdir – 默认临时文件路径 10 --> 11 <diskStore path="java.io.tmpdir/Tmp_EhCache"/> 12 <!-- 13 defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。 14 --> 15 <!-- 16 name:缓存名称。 17 maxElementsInMemory:缓存最大数目 18 maxElementsOnDisk:硬盘最大缓存个数。 19 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 20 overflowToDisk:是否保存到磁盘,当系统宕机时 21 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 22 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 23 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. 24 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 25 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 26 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 27 clearOnFlush:内存数量最大时是否清除。 28 memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。 29 FIFO,first in first out,这个是大家最熟的,先进先出。 30 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 31 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 32 --> 33 <defaultCache 34 eternal="false" 35 maxElementsInMemory="10000" 36 overflowToDisk="false" 37 diskPersistent="false" 38 timeToIdleSeconds="1800" 39 timeToLiveSeconds="259200" 40 memoryStoreEvictionPolicy="LRU"/> 41 42 <cache 43 name="cloud_user" 44 eternal="false" 45 maxElementsInMemory="5000" 46 overflowToDisk="false" 47 diskPersistent="false" 48 timeToIdleSeconds="1800" 49 timeToLiveSeconds="1800" 50 memoryStoreEvictionPolicy="LRU"/> 51 52 </ehcache>
spring下载地址
https://repo.spring.io/release/org/springframework/spring/
总结:mybatis学习完毕,跟着学习的导师质量很高,对于目前我所学习的方面有很多收获。mybatis简单书写,复杂还不行,慢慢练习增加熟练度,应用在项目中。这只是第一步。
-------宝剑锋从磨砺出,梅花香自苦寒来。