Mybatis
1.创建一个空的maven项目
2. 配置依赖
<dependencies>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
3.删除src目录
4.创建新的模块
-
编写mybatis的核心配置文件
-
在resources文件夹下创建mybatis-config.xml文件
-
配置以下内容
<?xml version="1.0" encoding="UTF8" ?>!!!!!!!!!!!!!!!!!!是UTF8 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--configuration 核心配置文件--> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/><!--数据库驱动--> <property name="url" value="jdbc:mysql://localhost:3306/justplay?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/><!--数据库连接--> <property name="username" value="root"/><!--用户名--> <property name="password" value="zbw537680"/><!--密码--> </dataSource> </environment> </environments> </configuration>
-
-
编写mybatis工具类
//sqlSessionFactory 工厂模式 public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { //使用Mybatis获取SqlSessionFactory对象 String resource="mybatis-config.xml"; InputStream inputStream= Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //既然有了SqlSessionFactory ,顾名思义我们也就可以从中获得SQLSession的实例了. //SQLSession 完全包含了面向数据库执行SQL命令所需的所有方法. public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
-
编写代码
-
实体类(对应的属性名要和数据库中的一致,否则会查询不到)
public class User { private int id; private String name; private String password; public User(){ } public User(int id, String name, String password) { this.id = id; this.name = name; this.password = password; } 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 getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + '}'; } }
-
Dao接口
public interface UserDao { List<User> getUserList(); }
-
接口实现类配置文件和接口不同的是,这里不实现借口里的全部函数也是可以的
<?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"> <!--命名空间要绑定一个对应的Dao/Mapper接口--> <mapper namespace="com.zhang.dao.UserDao"> <!-- select 查询语句--> <select id="getUserList" resultType="com.zhang.pojo.User"> <!--id要和接口里的函数名一致,type要写全称--> select * from justplay.user </select> </mapper>
-
5.开始测试
- 编写代码
public class UserDaoTest {
@Test
public void test(){
//获取SqlSession 对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//执行sql 方式1
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.printf(user.toString());
}
//涉及到数据库的增删改的时候一定不要忘记提交事务!
sqlSession.commit();
//关闭SQLSession
sqlSession.close();
}
}
-
注意事项
org.apache.ibatis.binding.BindingException: Type interface com.zhang.dao.UserDao is not known to the MapperRegistry.异常的处理
解决方案:
在mybatis-config.xml添加相应的注册即可:
<mappers> <mapper resource="com/zhang/dao/UserMapper.xml"/> </mappers>
java.lang.ExceptionInInitializerError异常的处理
Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: 1 字节的 UTF-8 序列的字节 1 无效。
这个时候要把xml的encode属性改为UTF8即可
6. 万能的MAP
假设我们的实体类里有大量的属性,这个时候就可以使用map来简化操作
举例:
接口代码:
User findUser(Map<String,Object> map);
配置文件:
<select id="findUser" parameterType="map" resultType="com.zhang.pojo.User">
select * from justplay.user where name=#{username} and password=#{userpsd}
</select>
测试代码:
public void test2(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
Map<String,Object> map=new HashMap<String,Object>();
map.put("username","123");
map.put("userpsd","asd");
System.out.println(mapper.findUser(map));
}
总结:
Map传递参数,直接在sql中取出key即可!
对象传递参数,直接在sql中取对象的属性
只有一个基本类型的参数的情况下,可以直接在sql中取到(注意名字要对应!)
多个参数用map或者注解!
7. 配置文件进阶
Mybatis可以配置多种环境,但是每一个SQLSessionFectory实例只能选择一种环境.
学会使用配置多种运行环境!
Mybatis默认的失误管理器是JDBC,连接池:POOLED
1. 属性(properties)
我们可以通过properties属性来实现引用配置文件
这些属性都是可外部配置且可动态替换的,即可以在典型的java苏醒文件中配置,亦可以通过properties元素的子元素来传递,ext:[db.properties]
编写一个配置文件
url=jdbc:mysql://localhost:3306/justplay?useUnicode=true&useSSL=true
driver=com.mysql.jdbc.Driver
username=root
password=zbw537680
在xml中使用配置文件
注意! xml中每个标签的顺序是规定好的,不按照规定的顺序会报错
还有个要注意的一点是&符号在配置文件中不需要转意
<configuration> <!--引入外部配置文件--> <properties resource="db.properties"/> <environments default="development"> <environment id="development"> <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> </environments><!-- 每一个mapper.xml都要在这里注册!--> <mappers> <mapper resource="com/zhang/dao/UserMapper.xml"/> </mappers></configuration>
2. 类型别名(typeAliases)
方法1(直接起别名):
<!--给实体类起别名--><typeAliases> <typeAlias type="com.zhang.pojo.User" alias="User"/></typeAliases>
方法2:
也可以指定一个包名,Mybatis会在报名下面搜索需要的javaBean,比如扫描实体类的包,他的默认别名就是这个类的类名,首字母小写,如果类名有注解,
<!--给实体类起别名--><typeAliases> <package name="com.zhang.pojo"/></typeAliases>
3. 设置
<setting><setting/>
这个自己查文档吧…
有一些功能向的设置
4. 映射器(mappers)
MapperRegistry: 注册绑定我们的Mapper文件:
方式1(推荐):
通过路径绑定
<!-- 每一个mapper.xml都要在这里注册!--><mappers> <mapper resource="com/zhang/dao/UserMapper.xml"/></mappers>
方式2:
使用class文件绑定
<!-- 每一个mapper.xml都要在这里注册!--> <mappers> <mapper class="com.zhang.dao.UserMapper"/> </mappers>
注意,这种方式必须要求接口个xml文件在一个包下并且同名!!!
方式3:
<!-- 每一个mapper.xml都要在这里注册!-->
<mappers>
<package name="com.zhang.dao"/>
</mappers>
注意,这种方式必须要求接口个xml文件在一个包下并且同名!!!
5.生命周期
生命周期,和作用域,是至关重要的,因为错误的使用会导致非常严重的并发问题.
SqlSessionFactoryBuilder:
- 一旦创建了SQLSessionFactory就没用了,不需要它了
- 最好创建为局部变量
SqlSessionFactory:
- 说白了可以联想为数据库连接池
- SQLSessionFactory一旦被创建就应该在运行期间一直存在,没有理由丢弃它或者重新创建
- 最简单的就是用单例模式或静态单例模式
SQLSession:
- 每个线程都应该有他自己的Sqlsession
- 本质上就是对数据库的一个请求(需要关闭否则资源被占用)
- 它不是线程安全的,不能被共享,所以最佳作用域是请求或者方法作用域
8. 解决属性名和字段名不一致的问题
数据库的字段名和实体类的属性名不一致时会导致查询为空的现象或者衍生现象
解决方案:
-
起别名:最简单最笨的方法 select psd as password from …
-
resultmap:
<mapper namespace="com.zhang.dao.UserMapper"> <resultMap id="UserMap" type="User"> <result column="id" property="id"/> <result column="name" property="name"/> <result column="password" property="psd"/> </resultMap> <!-- select 查询语句--> <select id="findUser" parameterType="map" resultMap="UserMap"> select * from justplay.user where name=#{username} and password=#{userpsd} </select>
select里面配置的是resultMap!
resultMap里面type属性配置的是实体类的类名
result里面 column配置的是数据库的列名,property配置的是类的属性名
如果列名和属性名一致就不需要显示的配置,例如上面的id和name,所以我们就可以省略这两句.
如果世界总是这么简单就好了
9.日志
9.1 日志工厂
如果一个数据库操作出现了异常,我们需要拍错,日志是最好的帮手
在核心配置文件中添加(注意顺序问题):
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
9.2 log4j
什么是log4j?
- Log4j是Apache的一个开源项目
- 可以控制每一条日志的输出格式
- 可以通过一个配置文件来灵活地进行配置
首先在maven的pox.xml中配置文件:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
然后在核心配置文件中编写:
<settings>
<setting name="logImpl" value="log4j"/>
</settings>
创建log4j.properties文件并编写(这个配置文件不需要在核心配置文件中声明;所以名字一定要写对):
#将等级为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=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.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{yy-MM-dd}][%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
9.3简单使用log4j
编写下面的测试方法:
static Logger logger = Logger.getLogger(LogTest.class);
@Test
public void testLog4j(){
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");
}
注意:导入的包名是import org.apache.log4j.Logger
测试结果为:
[com.zhang.dao.LogTest]-info:进入了testLog4j
[com.zhang.dao.LogTest]-debug:进入了testLog4j
[com.zhang.dao.LogTest]-error:进入了testLog4j
日志对象: 参数为当前类的class
static Logger logger = Logger.getLogger(LogTest.class);
日志级别:
- info
- debug
- error
分页
为什么要分页? 答:减少数据的处理量;好看呀!
可以通过sql的limit关键字来实现
SELECT * FROM user LIMIT 0,2
-- 这里的0,2指的是从第9个开始每两条信息分一页
使用Mybaties实现分页,核心SQL
<select id="getUserByLimit" resultMap="UserMap" parameterType="map">
select * from justplay.user limit #{startIndex},#{size}
</select>
测试代码:
public void test3(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map=new HashMap<String,Object>();
int page=0;
map.put("startIndex",page);
map.put("size",2);
List<User> list=mapper.getUserByLimit(map);
while(list.size()!=0){
for(User i:list){
System.out.println(""+i);
}
page++;
map.replace("startIndex",page*2);
list=mapper.getUserByLimit(map);
}
}
方法2 Rowbound:
略
方法3 分页插件:
pagehelper
说明文档:https://pagehelper.github.io/docs/howtouse
10.使用注解开发(尽管简单但是不推荐)
1.面向接口编程
对于接口的理解:
- 定义(规范,约束) 和 实现的分离
- 接口的本身反映了系统设计人员对系统的抽象理解
- 接口应该有两类:
- 对个体的抽象,他可以地响应一个抽象类(abstract class)
- 对一个个体某一方面的抽象,即形成一个抽象面(interface)
- 一个个体可能有多个抽象面,抽象体和抽象面是有区别的
2.面向注解开发(本质是反射):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sDLHWUZ6-1624054590283)(C:\Users\张博文\AppData\Roaming\Typora\typora-user-images\image-20210613090811937.png)]
如果有多个参数,可以通过map,也可以通过注解(实际上注解才是本质?)
举例:
@Select("select * from user where name=#{name} and password=#{password}")
User findUser(@Param("name") String name,@Param("password") String psd);
11.Lombok
新版本的IDEAR直接下载了这个插件,我们只需要在POX.xml中直接添加依赖就行:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
这个插件很方便,可以省去我们大量的编写的重复的代码:
给实体类加上注解就可以生成对应的函数(这是咋实现的?)
注解类型
@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
其中在类前添加@Data:会生成 无参构造,get,set,tostring,hashcode,equals函数
@AllArgsConstructor:添加有参构造
@NoArgsConstructor添加无参构造
@Getter and @Setter可以放在字段上也可以放在类上
12. 多对一
例子:多名学生对应一位老师
1.创建数据库
CREATE TABLE if not exists `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');
CREATE TABLE if not exists`student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
insert INTO `student` (`id`, `name`, `tid`) VALUES (1, '小明', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, '小红', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, '小张', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, '小李', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, '小王', 1);
2.测试环境搭建
- 导入lombok
- 新建实体类Teacher,Student
- 简历Mapper接口
- 简历Mapper.XML文件
- 在核心配置文件中绑定我们的Mapper接口或者文件[方式很多]
- 测试查询是否成功
3.代码以及注意细节
Student类:
@Data
public class Student {
private int id;
private String name;
//学生需要关联一个老师
private Teacher teacher;
}
Teaher类:
@Data
public class Teacher {
int id;
String name;
}
<select id="getStudents" resultMap="s_t">
select * from student;
</select>
<select id="findTeacher" resultType="com.zhang.com.Pojo.Teacher" parameterType="int">
select * from teacher where id=#{id};
</select>
<resultMap id="s_t" type="com.zhang.com.Pojo.Student">
<association property="teacher" column="tid" javaType="com.zhang.com.Pojo.Teacher" select="findTeacher"/>
</resultMap>
写一下我自己的理解,因为我们要给Student实体类中的teacher对象赋值,但是没有之中基本类型,所以我们需要自定义resultmap来实现该功能,但是它不比基本类型改名就行,他需要再一次进行查询,所以我们通过association标签来实现这一点,其中column可以理解为查询要传入的值,javaType可以理解为返回值类型,select里面写查询就好
13 一对多
例子一个老师拥有多个学生!
数据库和环境配置同上,
Student类:
@Data
public class Student {
private int id;
private String name;
}
Teaher类:
@Data
public class Teacher {
int id;
String name;
List<Student> students;
}
方法1:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z7kENcDO-1624054590285)(C:\Users\张博文\AppData\Roaming\Typora\typora-user-images\image-20210615181452933.png)]
方法2(推荐):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fvHgBSUt-1624054590286)(C:\Users\张博文\AppData\Roaming\Typora\typora-user-images\image-20210615181521349.png)]
小结:
- 关联: association [多对一]
- 集合connection[一对多]
- javaType & ofType
- javatype用来指定实体类中属性的类型
- oftype用来指定映射到List或者集合中的pojo类型,泛型中的约束类型
注意点:
- 保证sql的可读性
- 注意一对多和多对一中属性名和字段的问题
- 如果问题不好排查.可以使用日志,建议使用Log4j
面试高频:
- Mysql引擎
- InnoDB底层原理
- 索引
- 索引优化
14.动态SQL
所谓动态语言就是在给定不同参数的情况下,自动使用不同的SQL语句,一般是添加一些限制条件
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
在这个例子中,当传入title的值是就位在原本的SQL后追加"AND title like #{title}"
choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql。类似于Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。
sql片段
很多时候同一点sql代码或者动态查询会反复出现,这个时候我们就可以使用sql片段来复用这部分代码,具体格式为:
<sql id="if-title-author">
<if test"title != null">
title=#{title}
</if>
<if test"author != null">
and author=#{author}
</if>
</sql>
使用sql片段:
<select id="findStudent" parameterType="map" resultMap="s_t">
select * from student
<include refid="if-title-author"/>
</select>
foreach
只是个自动生成sql的标签而已,举个例子:
<sql id="test">
<where>
<foreach collection="ids" item="id" open=" (" close=" )" separator=" or ">
id = #{id}
</foreach>
</where>
</sql>
其中collection表示序列,item 表示序列中每一位元素,open表示sql以什么开始,close表示sql以什么结束,separator表示分割符,所以如果个上述的ids={1,2,3};
这个sql片段就是 where (id = 1 or id=2 or id=3 )