学习视频
学习内容会引用部分狂神的微信公众号笔记
随看随记
- mybatis官方文档
- POJO (Plain Old Java Objects)老式java对象
- Data Access Objepts (DAOs ) 数据持久层对象
- ${}是statement #{} 是preparationstation
- 避免sql注入
- #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】
- INSERT INTO user (name) VALUES (#{name});
- INSERT INTO user (name) VALUES (?);
- ${} 的作用是直接进行字符串替换
- INSERT INTO user (name) VALUES (’${name}’);
- INSERT INTO user (name) VALUES (‘kuangshen’);
Mybatis
- MyBatis 是一款优秀的持久层框架
- 持久化是将程序数据在持久状态和瞬时状态间转换的机制。
- 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)
- 为什么需要持久化服务呢?那是由于内存本身的缺陷引起的
- 内存断电后数据会丢失 内存过于昂贵,
- 持久层 完成持久化工作的代码块 . ----> dao层 【DAO (Data Access Object) 数据访问对象】
第一个Mybatis
maven中 注意加上以下代码 可以把资源加载成class
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
-
步骤
- 写一个工具类,同时引用第二步骤的配置文件
package com.paleatta; 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; public class mybatis_util { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
- 写一个配置文件
<?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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis? useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/paleatta/dao/UserMapper.xml"/> </mappers> </configuration>
- 写实体类
package com.paleatta.pojo; public class User { private int id; private String name; private String pwd; @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public User(int id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } public User() { } }
- 写接口
package com.paleatta.dao; import com.paleatta.pojo.User; import java.util.List; public interface UserDao { List<User> getUserList(); }
- 写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.paleatta.dao.UserDao"> <select id="getUserList" resultType="com.paleatta.pojo.User"> select * from mybatis.user </select> </mapper>
- 写测试类
@test public void test(){ SqlSession sqlSession = null; try { sqlSession = mybatis_util.getSqlSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> userList= userDao.getUserList(); for (User user : userList) { System.out.println(user); } } catch (Exception e) { } finally { sqlSession.close(); }
- 写一个工具类
- 写一个配置文件
- 通过jdbc连接数据库
- 注意 每有一个接口有实现的mapper.xml文件 都需要在这里添加命名空间
- 写一个实体类
- 主要写数据中每一个对象都有什么样的属性
- 写一个接口
- 接口主要得到我们需要的结果集
- 写一个mapper.xml
- mapper.xml主要写sql语句 以及返回的结果集合
- 写一个测试类
- 对编写的dao进行测试
注意:
- 对编写的dao进行测试
- 关闭sqlSession很重要
- 在resulttype 和 parametertype 要写数据类型的全限定名
增删改查的实现
- 主要通过修改mapper中的配置文件来实现多种数据库的处理
- 根据接口的多态性,通过在测试函数中调用特定功能的接口函数,来实现特定功能的数据库操作
- 在通关增删改对数据库进行操作时,一定要sqlsession.commit() 进行事务提交
- 增删改查等语法的详细内容见Mysql学习笔记
insert into tableName ([字段1],[字段2],[字段3]) values (value1_1,value1_2,value1_3),(value2_1,value2_2,value2_3),(value3_1,value3_2,value3_3) ;
updata tableName set ([字段1]=value1 ,[字段2]=value2,[字段3]=value3) values where 条件;
delete from tableName where 条件;
-
SELECT DISTINCT <select_list> #(去重 注意取别名) FROM <left_table> <join_type> JOIN <right_table> ON <join_condition> WHERE <where_condition> #(具体的值,子查询语句) GROUP BY <group_by_list> #(通过什么字段进行分组) HAVING <having_condition> #(过滤后的分组信息) ORDER BY <order_by_condition> #(排序顺序 ASC 升序 DESC 降序) LIMIT <limit_number> #(从哪条开始 罗列多少条数据)
- 可能会出现的问题
- 在mybatis-config.xml中<mapper>标签是需要使用路径即\而不能使用点
- 在增加中文注释后 出现的bug介绍 :1 字节的 UTF-8 序列的字节 1 无效。将所在的xml的编码方式改成utf8即可。
map方式的参数传递
- 思想:将原有的通过特定参数类型的方式全部转换成使用map(键值对)形式进行存储和传输,通过对键值对中的集合进行赋值,来实现对特定字段进行sql操作。
- 以添加为例
int insertUserByMap(Map<String,Object> map);
<insert id="insertUserByMap" parameterType="map">
insert into mybatis.user (id , pwd , name ) values (#{id} , #{possward} , #{name});
</insert>
SqlSession sqlSession = mybatis_util.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
User user = new User();
Map<String, Object> map = new HashMap<String, Object>();
map.put("id",6);
map.put("possward","qwerty");
map.put("name","行号");
mapper.insertUserByMap(map);
sqlSession.commit();
sqlSession.close();
将一个对象的多个字段转化成一个map集合中的多个数据类型和数据,将整个map对象进行sql语句进行操作 其优点在于不需要考虑传递对象的数据类型,只需要在map中设置好数据类型即可。
通过对象传递参数 sql会获取其对象的属性 [parameterType=“Object”]
通过map传递参数 sql会获取其key值 [parameterType=“map”]
模糊搜索
-
java 代码执行时传递通配符
map.put("name", "%行%");
-
sql拼接中使用通配符
select * from mybatis.user where name like "%"#{name}"%";
属性优化 外部引入db.properties属性
-
在mybatis-config.xml中的各种标签是有一定顺序的
-
通过在外部db.properties文件中进行配置,然后导入到xml中进行配置
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/jdbcstudy? useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username=root password=123456
<properties resource="org/mybatis/example/config.properties"> <property name="username" value="dev_user"/> <property name="password" value="F2Fa3!33TYyg"/> </properties> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment>
-
如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
- 首先读取在 properties 元素体内指定的属性。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
- 因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。
别名优化
- 实体类优化形式
<typeAliases> <package name="com.paleatta.pojo"/> </typeAliases>
- 但是只有实体类的优化形式无法对实体类进行其他别名的命名,可以通过在实体类上加@Alias(“user”) 可以对该实体类进行自定义别名限定
@Alias("user")
public class User {
映射器说明(mapper)
使用mapper标签进行映射器配置,除此之外还有包名和类名两种方法,但不推荐
<mappers>
<mapper resource="com/paleatta/dao/Mapper.xml"/>
</mappers>
解决属性名和字段名不一致的问题 resultMap
- 通过在resultmap中将数据库中的pwd字段映射到User实体类中的possword字段上,对于字段和属性相同的部分可以不予以设定
<resultMap id="userMap" type="user">
<result column="pwd" property="possword"/>
</resultMap>
<select id="getUserList" resultMap="userMap">
select * from mybatis.user;
</select>
日志
- 标准日志输出 STDOUT_LOGGING 可以输出所有的日志数据,但是有很多事我们不需要的,可以直接在xml中作为属性名直接用
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
Log4j日志输出
-
导入依赖
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
配置文件(先用着狂神的,稍微改了一下,以后再看看别的配置)
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码 log4j.rootLogger=DEBUG,console,file #控制台输出的相关设置 log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold=DEBUG log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%c]-%m%n #文件输出的相关设置 log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File=./log/paleatta.log log4j.appender.file.MaxFileSize=10mb log4j.appender.file.Threshold=DEBUG log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%c]%m%n #日志输出级别 log4j.logger.org.mybatis=DEBUG log4j.logger.java.sql=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.ResultSet=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG
-
setting设置日志实现
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
-
在程序中使用Log4j进行输出
//注意导包: import org.apache.log4j.Logger public class test { static Logger logger = Logger.getLogger(test.class); @Test public void test1(){ SqlSession sqlSession = mybatis_util.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); for (User user : userList) { logger.info(user); } sqlSession.close(); } }
- 注意:有时idea无法打开生成的日志文件 这是因为在日志文件中存在乱码所致,而日志文件中的乱码时 由于在<mapper> 或<typeAliases>中使用package 扫描形式导致的,但是网上也没有比较好的解决方案,因此如果想在idea上看log文件加就不可以使用 package扫描的方法。如果必须要使用的话可以在普通的记事本文件中进行查看。
- 在以后的输出过程中 一般会以debug为主体,在某些特定的位置输出info或error信息来进行提示或警示客户。
分页
- sql语句limit进行分页
SELECT * FROM TABLE_NAME LIMIT #{START_PAGE},#{PAGE_SIZE}
SELECT * FROM TABLE_NAME LIMIT #{PAGE_SIZE}
通过在test中的map来对其进行赋值从而实现分页
- 分页插件mybatis-PageHelper
mybatis流程图
- 自动事务提交开启,可以通过在 getSqlSession() 中将其参数放置为true即可自动实现事务提交。
注解开发
-
一般是使用注解配合XML文件一同开发
-
在选用mapper映射器的时候,有注解,需要使用类映射器,而不能和使用xml映射器
@Results(id = "resultMap" , value = { @Result(property = "possword",column ="pwd")}) @Select("select * from mybatis.user") List<User> getUserList(); @ResultMap(value = "resultMap") @Select("select * from mybatis.user limit #{startPage},#{pageSize};") List<User> getUserListByLimit(Map<String,Integer> map);
-
@results== resultMap @result== result通过定义在result中的映射关系来进行映射,当其他接口也组要这么映射时,通过定一个id,并通过resultMap其寻找其id,即可避免重复设置映射关系,上述注解开发与下述xml表达的是同一个意思。
<resultMap id="userMap" type="user"> <result column="pwd" property="possword"/> </resultMap> <select id="getUserList" resultMap="userMap"> select * from mybatis.user; </select> <select id="getUserListByLimit" parameterType="map" resultMap="userMap"> select * from mybatis.user limit #{startPage},#{pageSize}; </select>
注解增删改查
- 当传递多个参数时,除了像上一个通过map键值对的形式进行传输,还可以通过直接传参数的形式,在查找参数时,mybatis会寻找param中的变量名,而在主函数中显示的提示信息依旧是int后的变量名,这样就可以实现sql语句中的变量名和测试函数中传递的变量名相分离的功能进行解耦。
@ResultMap(value = "resultMap") @Select("select * from mybatis.user limit #{startPage},#{pageSize};") List<User> getUserListByLimit1(@Param("startPage") int startPage1, @Param("pageSize") int pageSize1);
- @Param注解用于给方法参数起一个名字。以下是总结的使用原则:
- 在方法只接受一个参数的情况下,可以不使用@Param。
- 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
- 如果参数是 JavaBean , 则不能使用@Param。
- 不使用@Param注解时,参数只能有一个,并且是Javabean。
Lombok
- 常用注解
- @data 自动生成无参构造、get、set函数、tostring、equals和hashcode
- @AllArgsConstructor自动生成全参构造
- @NoArgsConstructor
- @EqualsAndHashCode
- @ToString
- @Setter
- @Getter
- 如果想偷懒的话,可以只使用三个加深的注释即可实现所有的配置,但是如果只需要部分进行get或set的,则需要将这些注解放置到这些成员变量即可。注意,对于@data来说,不仅把private的成员变量进行处理,还会对public的成员变量进行get和set处理。
多对一与一对多
多对一
- 一般一对多的关系都有多端进行关系维护 谁建外键谁维护
-
按照查询嵌套处理(子查询)
<select id="studentListByTid2" resultMap="studentTeacher2"> select * from student; </select> <resultMap id="studentTeacher2" type="student"> <result property="id" column="id" /> <result property="name" column="name" /> <association property="teacher" column="tid" javaType="teacher" select="getTeacher"/> </resultMap> <select id="getTeacher" resultType="teacher"> select * from teacher where id = #{id} </select>
相当于对一个sql语句进行了拆分,在查询Student中又进行了Teacher的子查询
-
按照结果嵌套处理(关联查询)
注释中没有@association,因此其只能在xml中使用<resultMap id="studentTeacher" type="student"> <result column="id" property="id" /> <result column="sname" property="name" /> <association property="teacher" javaType="Teacher"> <result column="tname" property="name" /> </association> </resultMap> <select id="studentListByTid" resultMap="studentTeacher"> select s.id id, s.name sname, t.name tname from student s, teacher t where s.tid = t.id; </select>
- 注意在按照结果嵌套处理时,当去了别名以后,要把其别名作为数据库的字段进行处理
- association标签相当于把在teacher表格中寻找字段tname,映射到实体类中的name属性
- 其中的javaTpye有点类似与进行表关联时的关联的另一张表
一对多
个人认为按照结果查询的代码可读性更高,因此在此仅对按结果进行记录
<select id="getTeacherByTid" resultMap="TeacherStudent">
select s.id id , s.name sname, t.name tname, t.id tid
from student s , teacher t
where s.tid = t.id;
</select>
<resultMap id="TeacherStudent" type="teacher">
<result property="teacherId" column="tid" />
<result property="name" column="tname" />
<collection property="studentList" ofType="student">
<result property="id" column="id" />
<result property="name" column="sname" />
<result property="tid" column="tid" >
</collection>
</resultMap>
- 其中ofType与javaType功能相似,可以认为是与需要关联的另一个表。
小思考
- 在什么情况下实体类中使用列表作为成员变量,而什么时候在接口中的方法返回值中使用列表?
- 对于多对一 通过多个学生的id去查找他们的老师 其输出结果为多个学生,多个学生则需要一个列表来对其进行储存。学生作为一个实体类,老师为其一个成员变量。
- 对于一对多 通过一个老师的id去查找他的学生 其输出结果为一个老师,老师只有一个,因此不需要列表作为返回值。老师作为一个实体类,有多个学生,需要一个列表作为老师的成员变量。
动态sql
if
- 通过可以动态添加SQL的方式避免写多种重载的接口函数
- 注意where 1=1 很重要 这可以保证无论是否在什么条件下都可以进行查询,而且在后面的if标签中的and都要加,否则就会在and加或不加出现问题
- 通过在test中添加我们要判断的条件即可
<select id="getBlog" parameterType="map" resultType="blog"> select * from blog where 1=1 <if test="title != null"> and title = #{title} </if> <if test=" author != null"> and author = #{author} </if> </select>
choose (when, otherwise)
-
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。choose只能执行一个条件
-
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
-
因为choose类似一个带有break的switch语句,一次执行的过程中只能执行一个when或者otherwise因此and 加或不加都可以
<select id="getBlog3" parameterType="map" resultType="blog"> select * from blog <where> <choose> <when test="title != null"> title = #{title} </when> <when test=" author != null"> and author = #{author} </when> <otherwise> and views = #{views} </otherwise> </choose> </where> </select>
trim (where, set)
-
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
<select id="getBlog2" parameterType="map" resultType="blog"> select * from blog <where> <if test="title != null"> title = #{title} </if> <if test=" author != null"> and author = #{author} </if> </where> </select>
- 当map中只执行map.put(“author” , “狂神说”); 时 其拼接的sql语句自动去除and为
select * from blog WHERE author = ?
- set与where类似,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
<update id="updateBlog" parameterType="map"> update mybatis.blog <set> <if test="title != null"> title = #{title}, </if> <if test="author != null"> author = #{author} </if> </set> where id = "cca2ae70 6ca5 44d4 b0b0 d5588e6d8896" </update>
- 当map中只执行map.put(“title” , “Mybatis如此简单2”) 时 其拼接的sql语句自动去除“,“为
update mybatis.blog SET title = ? where id = "cca2ae70 6ca5 44d4 b0b0 d5588e6d8896"
SQL片段
-
作用帮助实现代码复用
-
在上面的演示中会发现很多重复代码,可以通过sql标签将其打包,在后面通过include标签从而来实现sql标签中sql语句的复用。
<sql id="if-title-author"> <if test="title != null"> title = #{title} </if> <if test=" author != null"> and author = #{author} </if> </sql>
<select id="getBlog2" parameterType="map" resultType="blog"> select * from blog <where> <include refid="if-title-author"></include> </where> </select>
foreach
-
其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。
select * from mybatis.blog where id=1 or id=2 or id=3
<select id="getBlogByForeach" parameterType="map" resultType="blog"> select * from blog <where> <foreach item="id" collection="ids" open="(" separator=" or " close=")"> id = #{id} </foreach> </where> </select>
-
需要注意一下,在foreach中多了一个集合变量,这个集合变量也需要在建立好以后,在map中put进去。
ArrayList<Integer> ids = new ArrayList<Integer>(); ids.add(1); ids.add(2); map.put("ids", ids);
-
我觉得可以更加深入的一下map输入,类似与vue框架中的双向绑定,只不过在mybatis中只能将test函数中的map值绑定到sql区域中的指定#{ }中的区域,将map中的右侧绑定到sql语句中的#{ }的区域。