概述
什么是框架?
框架是我们软件开发中的一套解决方案,不同的框架解决不同的问题。
使用框架的好处:框架封装了很多的细节,使开发者可以用极简的方式实现功能。大大提高开发效率。
三层架构
表现层:用于展示数据
业务层:处理业务需求(service)
持久层:和数据库交互
持久层技术解决方案
JDBC技术:
Connection
preparedStatement
ResultSet
Spring的JdbcTemplate:
Spring中对jdbc的简单封装
Apache的DBUtils:
它和Spring的JdbcTemplate很像,也是对Jdbc的简单封装
以上这些都不是框架
JDBC是规范
Spring的JdbcTemplate和Apache的DBUtils都只是工具类
Mybatis
Mybatis官方文档
Mybatis官网地址:https://mybatis.org/mybatis-3/zh/getting-started.html
官方网站始终是最好的学习方式
[Mybatis官网链接](mybatis – MyBatis 3 | 入门 )
[Mybtais百度百科](MyBatis_百度百科 (baidu.com) )
第一个Mybatis程序:搭建环境 => 导入Mybatis => 编写代码 => 测试
Mybatis概述
mybatis是一个持久层框架,用java编写的
它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等繁杂过程
它使用了ORM思想实现了结果集的封装
ORM:
Object Relational Mapping 对象关系映射
简单的说:就是把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表
第一个Mybatis程序
环境配置
-
新建一个普通的maven项目
-
删除src(使之成为父工程)并导入依赖,pom.xml代码如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <!-- 父工程 --> <groupId>com.yao</groupId> <artifactId>mvn-mybatis</artifactId> <version>1.0-SNAPSHOT</version> <!-- 导入依赖 --> <dependencies> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</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> </project>
-
创建模块
-
编写mybatis的核心配置文件(在resource目录下new一个file,mybatis-config.xml)
<?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> <!--环境配置,连接的数据库,这里使用的是MySQL--> <environments default="mysql"> <environment id="mysql"> <!--指定事务管理的类型,这里简单使用Java的JDBC的提交和回滚设置--> <transactionManager type="JDBC"></transactionManager> <!--dataSource 指连接源配置,POOLED是JDBC连接对象的数据源连接池的实现--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=true&useUnicode=true&characterEncoding=UTF-8"></property> <property name="username" value="root"></property> <property name="password" value="123456"></property> </dataSource> </environment> </environments> <!-- 每一个Mapper.xml一定要在这里进行注册! --> <mappers> <mapper resource="com/yao/dao/AccountMapper.xml"/> </mappers> </configuration>
-
编写mybatis的工具类(在项目中建一个工具包(utils)目录下)
package com.yao.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; //sqlSessionFactory -> sqlSession public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static{ try { //获取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(); } }
-
编写代码
-
Pojo(实体类)
package com.yao.pojo; public class Account { private int id; private String name; private double money; public Account(){} public Account(int id, String name, double money) { this.id = id; this.name = name; this.money = money; } public int getId() { return id; } public String getName() { return name; } public double getMoney() { return money; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setMoney(double money) { this.money = money; } @Override public String toString() { return "account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
-
AccountDao
package com.yao.dao; import com.yao.pojo.Account; import java.util.List; public interface AccountDao { List<Account> getList(); }
-
AccountMapper.xml
<?xml version="1.0" encoding="UTF8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace相当于绑定一个对应的Mapper/dao接口 --> <mapper namespace="com.yao.dao.AccountDao"> <!-- id代表Mapper/dao中的方法名,resultType是返回的结果,而且要写全限定类名 --> <select id="getList" resultType="com.yao.pojo.Account"> select * from test.account </select> </mapper>
在父模块和子模块的pom.xml中加入
<!--在build中配置resources,来防止我们资源导出失败的问题--> <build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
测试
AccountDaoTest
package com.yao.dao;
import com.yao.pojo.Account;
import com.yao.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class AccountDaoTest {
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
List<Account> accountList = accountDao.getList();
for(Account account : accountList){
System.out.println(account);
}
sqlSession.close();
}
}
(PS:有时候会报 error building SqlSession,将每个mapper配置文件encoding="UTF-8"全部改为encoding="UTF8"就能成功运行)
CRUD
其实就是增删改查
-
增加(Create)
-
读取查询(Retrieve)
-
更新(Update)
-
删除(Delete)
Mapper
List<Account> getList();
Account getAccountById(int id);
int addAccount(Account account);
int deleteAccount(int id);
Mapper.xml
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace相当于绑定一个对应的Mapper/dao接口 -->
<mapper namespace="com.yao.dao.AccountDao">
<!-- id代表Mapper/dao中的方法名,resultType是返回的结果,而且要写全限定类名 -->
<select id="getList" resultType="com.yao.pojo.Account">
select * from test.account
</select>
<select id="getAccountById" parameterType="int" resultType="com.yao.pojo.Account">
select * from test.account where id = #{id}
</select>
<insert id="addAccount" parameterType="com.yao.pojo.Account">
insert into test.account (id,aname,money) values(#{id},#{aname},#{money})
</insert>
<delete id="deleteAccount" parameterType="int" >
delete from test.account where id=#{id}
</delete>
</mapper>
Test
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
List<Account> accountList = accountDao.getList();
for(Account account : accountList){
System.out.println(account);
}
sqlSession.close();
}
@Test
public void TestGetAccountById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountDao mapper = sqlSession.getMapper(AccountDao.class);
Account account = mapper.getAccountById(10);
System.out.println(account);
sqlSession.close();
}
@Test
public void TestAddAccount(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountDao mapper = sqlSession.getMapper(AccountDao.class);
mapper.addAccount(new Account(11,"哈哈",3000));
//插入必须得提交事务才能插入成功
sqlSession.commit();
sqlSession.close();
}
@Test
public void delete(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountDao mapper = sqlSession.getMapper(AccountDao.class);
mapper.deleteAccount(11);
sqlSession.commit();
sqlSession.close();
}
###CRUD总结
- namespace中的包名要和Mapper/Dao接口保持一致
- resultType:Sql语句执行返回的类型(除了class就是基本数据类型)
- parameterType:参数类型
- 更新,删除,插入操作都需要sqlSession.commit()提交事务,否则就无法改变数据库里的记录
- pojo类里面的属性可以拆开;在xml配置文件里的CRUD标签用#{xx}接受到Mapper里方法传过来的参数,且参数名需保持一致
Mybatis执行流程
参考链接:https://blog.csdn.net/qq_38270106/article/details/93398694
Map
假设,我们的实体类,或者数据库中的表,字段或者参数过多,我们应该考虑用map去实现数据库的操作(企业开发中)
例子:
// Mapper接口
int updateAccount(Map<String,Object> map);
<!-- Mapper对应的xml文件 -->
<update id="updateAccount" parameterType="map" >
update test.account set aname=#{aname} where id=#{id}
</update>
//Test
@Test
public void TestUpdate(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountDao mapper = sqlSession.getMapper(AccountDao.class);
Map<String,Object> map = new HashMap<String,Object>();
map.put("id",10);
map.put("aname","潘潘");
mapper.updateAccount(map);
sqlSession.commit();
sqlSession.close();
}
Map传递参数时,直接在sql中取出key即可(parameterType=“map”)
多个参数适合用map(非正式),或者注解
配置解析
1、核心配置文件
-
mybatis-config.xml
-
Mybatis的配置文件包含了会深深影响Mybatis行为的设置和属性信息
configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器)
1、环境配置(environments)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:
- 每个数据库对应一个 SqlSessionFactory 实例
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
如果忽略了环境参数,那么将会加载默认环境
environments 元素定义了如何配置环境
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<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>
注意一些关键点:
- 默认使用的环境 ID(比如:default=“development”)。
- 每个 environment 元素定义的环境 ID(比如:id=“development”)。
- 事务管理器的配置(比如:type=“JDBC”)。
- 数据源的配置(比如:type=“POOLED”)。
默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]") ,默认事务管理是JDBC
- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
- MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
如果使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用TransactionFactory 接口实现类的全限定名或类型别名代替它们。
数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
- 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"),默认数据源类型是POOLED
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
此外,对于**“池”**,如果每次连数据库都需要创建一次连接,是非常占用资源的,一个用户连完后先不要关闭它,等待下一个用户继续连,直到最后可以回收(用完后可以回收)
2、属性(properties)
我们可以通过properties属性(在resources目录下)来实现引用配置文件
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。
编写一个配置文件(resources目录下)
db.properties(Java 属性文件中配置这些属性)
driver = com.mysql.jdbc.driver
url = jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF8
username = root
password = 123456
在配置文件映入时,所有的xml文件都有规定标签的顺序,例如properties只能放在最上面
然后在核心配置文件中配置properties,讲db.properties导进来
<properties resource="db.properties"/>
官方文档
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
如果有相同的字段,则优先使用外部配置文件的字段
设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
3、类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
当这样配置时,Blog
可以用在任何使用 domain.blog.Blog
的地方。
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
扫描包的情况下,别名就是该实体类的类名全部小写;一般来说,对于类比较少的情况下,我们使用第一种,类比较多的话我们使用第二种
每一个在包 domain.blog
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author
的别名为 author
;若有注解,则别名为其注解值。例如:
@Alias("author")
public class Author {
...
}
4、设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
具体内容参考官方文档
5、映射器(mappers)
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:///
形式的 URL),或类名和包名等。例如:
方法一
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
方法二
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
方法二注意事项:
- 接口和它的Mapper配置文件必须同名!
- 接口和它的Mapper配置文件必须在同一个包下!
使用扫描包进行注入绑定注意的点也跟上述一样,一般情况下都是用resource,即方法一进行绑定。
6、其他配置
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- mybatis-generator-core
- mybatis-plus(mybatis再简化,增删改查都不用写,只用写复杂得实现)
- 通用mapper
作用域(Scope)和生命周期
生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题
SqlSessionFactoryBuilder:
- 一旦创建了SqlSessionFactoryBuilder,就不再需要它了
- 放在局部变量
SqlSessionFactory:
- 可以想象成数据连接池
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- 因此SqlSessionFactory的最佳作用域是应用作用域(全局变量)
- 最简单的就是使用单例模式或者静态单例模式
SqlSession
- 连接到连接池的一个请求!
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
- 用完之后需要赶紧关闭,否则资源被占用
解决属性名和字段名不一致的问题
数据库:
id name pwd
实体类:
id name password
解决办法一:在SQL语句里面起别名,使查出来的pwd是password,从而可以构造正常的实体类
解决办法二:
resultMap:
结果集映射
<!-- 结果集映射 -->
<resultMap id="UserMap" type="User">
<!-- column是数据库中的字段,properties是实体类中的属性(id,name可以不写) -->
<result column="id" properties="id"/>
<result column="name" properties="name"/>
<result column="pwd" properties="password"/>
</resultMap>
<select id="xxx" resultMap="UserMap">
<!-- 代码省略 -->
</select>
其实只要映射不一样的属性就可以了,其他的属性可以不写
实际上UserMap是不存在的返回类型,但它通过映射找到对应id的映射集,在该映射集里规定数据库和实体类属性的映射关系
日志
1、日志工厂(setting)
如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手!
曾经:sout,debug
现在我们用日志工厂来辅助我们开发:
- SLF4J
- LOG4J
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING(标准日志工厂)
- NO_LOGGING
在Mybatis中具体使用哪一个日志实现,在设置中设定
STDOUT_LOGGING是标准的日志输出,什么都不用配
设置名 | 描述 | 有效值 |
---|---|---|
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J(deprecated since 3.5.9) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING |
logImpl没有默认值
例:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
Opening JDBC Connection
Created connection 1858609436.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6ec8211c]
==> Preparing: select * from test.account
==> Parameters:
<== Columns: id, aname, money
<== Row: 10, 潘潘, 3000
<== Row: 11, 哈哈, 3000
<== Row: 20, 姚姚, 200
<== Row: 30, 赛文, 0
<== Row: 40, 老大, 10
<== Total: 5
account{id=10, name='潘潘', money=3000.0}
account{id=11, name='哈哈', money=3000.0}
account{id=20, name='姚姚', money=200.0}
account{id=30, name='赛文', money=0.0}
account{id=40, name='老大', money=10.0}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6ec8211c]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6ec8211c]
Returned connection 1858609436 to pool.
2、LOG4J
[log4j百度百科](log4j_百度百科 (baidu.com) )
什么是LOG4J?
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
- 也可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
- 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
mvn仓库网址:https://mvnrepository.com
1、导入log4j依赖(查看[mvn库](Maven Repository: log4j » log4j » 1.2.17 (mvnrepository.com) ))
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、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
3、配置log4j为日志的实现
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
4、log4j的使用
1.在要使用Log4j的类中,导入包import org.apache.log4j.Logger;
2.日志对象,加载参数为当前类的class
static Logger logger = Logger.getLogger(LogDemo.class); //LogDemo为相关的类
3.日志级别
logger.info("xxx");
logger.debug("xxx");
logger.error("xxx");
分页
使用Limit分页
select * from user limit startIndex,pageSize
select * from user limit n #[0,n]
limit后接两个参数是从数据库查询的起始记录和终止记录,分页是为了减少数据的处理量
用Mybatis实现分页
Mapper接口
List<pojo> = getPojoByLimit(Map<String ,Integer> map);
Mapper.xml
<select id="getPojoByLimit" parameterType="map" resultType="pojo">
select * from test.account limit #{startIndex},#{pageSize}
</select>
Test类
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
HashMap<String,Integer> map = new HashMap<String,Integer>();
map.put("startIndex",0);
map.put("pageSize",2);
List<Account> accountList = mapper.getAccountByLimit(map);
accountList.for{
sout(account);
}
sqlSession.close();
}
RowBounds(了解即可)
通过java代码层面实现分页
Mapper接口
List<pojo> = getPojoByRowBounds();
Mapper.xml
<select id="getPojoByRowBounds" resultType="pojo">
select * from test.account
</select>
Test类
SqlSession sqlSession = MybatisUtils.getSqlSession();
//RowBounds实现(从第一页开始,查询两个)
RowBounds rowBounds = new RowBounds(1,2);
List<pojo> pojoList = sqlSession.selectList("全路径类名.方法名",null,rowBounds);
pojoList.for{
sout(pojo);
}
sqlSession.close();
分页插件(pageHelper)
了解即可,知道是啥
使用注解开发
Mapper
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
xml
<!-- 绑定接口 -->
<mappers>
<mapper class="全限定类名"></mapper>
</mappers>
最后测试
关于注解:
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
注解开发的本质是:反射机制的实现
底层是用了动态代理
CRUD
我们可以在工具类创建的时候实现自动提交事务
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
当里面写了true时,就不用再写sqlSession.commit()了
例:select
Mapper接口
// 方法存在多个参数,所有的参数前面必须加上@Param("a")注解,仅限基本类型,单个参数无所谓
@Select(select * from table where id = #{a})
Pojo getPojoByID(@Param("a") int xx)
Mapper.xml文件里面得sql也得跟注解里名字保持一致才能查到
如果参数是pojo的话,sql语句里面可以直接用#{}获取里面的元素
记得绑定接口!
增删改,略
关于@Param注解
- 基本类型的参数或者String类型,需要加上
- 引用类型不需要加
- 如果只有一个基本类型,可以忽略,但是建议加上
- 我们在sql中引用的就是我们这里的@Param中设定的属性名
#{}和${}区别
参考链接:https://www.cnblogs.com/liaowenhui/p/12217959.html
使用 #{} 可以有效的防止SQL注入,提高系统安全性
能用#{}尽量用#{};${}不能有效防止sql注入,不安全
Lombok
Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法.
参考链接:https://www.jianshu.com/p/365ea41b3573
- java library(java库)
- plugins(插件)
- build tools(构造工具)
- with one annotation your class(只需一个注释就可以创建您的类)
使用步骤:
1、在IDEA中安装Lombok插件
2、在项目中导入lombok的jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R7AFoMsI-1642851887234)(C:\Users\17535\Desktop\MyNotes\SSM笔记\picture\1642753462327.png)]
@Data://无参构造,get,set,toString,hashcode,equals
@ToString //tostring方法
@EqualsAndHashCode
@AllArgsConstructor //有参构造
@NoArgsConstructor //无参构造
@Getter and @Setter //放在实体类上是所有变量的set和get方法,放一个字段上只是一个变量的set和get
关于Lombok
虽然Lombok可以帮助我们省去很多冗余代码,但还是不建议使用。
[为什么要放弃使用Lombok](为什么要放弃 Lombok ? - 知乎 (zhihu.com) )
多对一处理
查询处理
查询所有的学生信息,以及对应的老师的信息
思路:
- 查询所有的学生信息
- 根据学生的tid,找到对应的老师
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace相当于绑定一个对应的Mapper/dao接口 -->
<mapper namespace="com.yao.dao.StudentMapper">
<resultMap id="StudentTeacher" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<select id="getStudent" resultMap="StudentTeacher">
select * from test.student
</select>
<select id="getTeacher" resultType="teacher">
select * from test.teacher where id = #{id}
</select>
</mapper>
对于简单的属性,我们可以用result来映射,但对于复杂的属性,比如这时候的一个对象,我们需要单独处理:
对象:association
集合:collection
按照结果嵌套处理
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid=t.id;
</select>
<resultMap id="StudentTeacher2" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
测试:
@Test
public void testStudent2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudent2();
for (Student student : studentList) {
System.out.println(student);
}
sqlSession.close();
}
个人理解:
- property是pojo内的元素
- column则代表数据库查出来时的列名
- 在association里,如果有需要也要进行嵌套映射
- 在结果嵌套查询里多关注sql的select列名,column需和它们保持一致
多对一处理
实体类
@Data
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
Mapper接口:
public interface TeacherMapper {
Teacher getTeacher(@Param("tid") int id);
}
Mapper.xml
<!-- namespace相当于绑定一个对应的Mapper/dao接口 -->
<mapper namespace="com.yao.dao.TeacherMapper">
<!-- id代表Mapper/dao中的方法名,resultType是返回的结果,而且要写全限定类名 -->
<select id="getTeacher" resultMap="TS">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid=t.id and t.id=#{tid}
</select>
<resultMap id="TS" type="teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
</mapper>
测试:
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher);
sqlSession.close();
}
小结:
-
对象-association【多对一】
-
集合-collection【一对多】
-
javaType和ofType:
- javaType用来指定实体类中属性的类型
- ofType用来指定映射到List或者集合中的Pojo类型,泛型中的约束类型!
-
出现错误建议用log4j进行排错
动态SQL
什么是动态SQL?
动态SQL就是根据不同的条件生成不同的SQL语句
所谓动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
IF
Mapper类
List<Student> sIf(Map map);
Mapper.xml
<select id="sIf" parameterType="map" resultType="student">
select * from test.student where 1=1
<if test="sid != null">
and id = #{sid}
</if>
<if test="sname != null">
and name = #{sname}
</if>
</select>
测试
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
HashMap map = new HashMap();
map.put("sid",1);
map.put("sname","小明");
List<Student> studentList = mapper.sIf(map);
for (Student student : studentList) {
System.out.println(student);
}
sqlSession.close();
}
choose (when, otherwise)
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)。
<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>
(摘自[动态SQL](mybatis – MyBatis 3 | 动态 SQL ))
trim (where, set)
前面几个例子已经方便地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:
SELECT * FROM BLOG
WHERE
这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。
MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
来看看与 set 元素等价的自定义 trim 元素吧:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
注意,我们覆盖了后缀值设置,并且自定义了前缀值。
(摘自[动态SQL](mybatis – MyBatis 3 | 动态 SQL ))
SQL片段
<sql id="testt">
select * from test.student where 1=1
<if test="sid != null">
and id = #{sid}
</if>
<if test="sname != null">
and name = #{sname}
</if>
</sql>
<select id="sIf" parameterType="map" resultType="student">
<include refid="testt"/>
</select>
这样的格式也能跑
- 这方便我们有些代码的复用
- 最好基于单表来定义SQL片段
- 不要存在where标签
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
(摘自动态SQL)
实例:
sql:
select * from student where id=1 or id=2
Mapper接口:
List<Student> sForeach(Map map);
Mapper.xml
<select id="sForeach" parameterType="map" resultType="student">
select * from test.student
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
测试:
@Test
public void testForEach(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
HashMap map = new HashMap();
ArrayList<Integer> ids = new ArrayList<Integer>();
//动态查找
ids.add(1);
ids.add(2);
map.put("ids",ids);
List<Student> studentList = mapper.sForeach(map);
for (Student student : studentList) {
System.out.println(student);
}
sqlSession.close();
}
提要:
- item是迭代项
- 通过collection使用方法中的参数
- open是开始
- close是结束
- separator是用什么分隔
补充知识
面试高频:
Mysql引擎、InnoDB底层原理、索引、索引优化
去掉下划线:@SuppressWarnings(“all”) //抑制警告
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | falss |
---|---|---|
<setting name="mapUnderscoreToCamelCase" value="true"/>
-
驼峰命名映射只适用于从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
-
例如java属性名是createTime,数据库列名是create_Time,这种情况下就能自动完成映射,而不用去使用resultMap手动映射
缓存
查询需要连接数据库,如果多次查询的话非常耗费资源
如果我们将一次查询的结果放在一块内存里(缓存),
当我们再次查询相同数据时,直接走缓存,就不用再去做数据库的相关操作了,效率大大提升
简介
什么是缓存?
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
为什么使用缓存?
- 为了减少和数据库的交互次数,减少系统开销,提高系统效率
什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据
Mybatis缓存
- Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率
- Mybatis系统中默认定义了两级缓存:
- 一级缓存
- 二级缓存
- 默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存
- 为了提高扩展性,Mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
一级缓存
-
一级缓存也叫本地缓存:SqlSession
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 如果以后需要相同的数据,直接去缓存中拿,没必要再去查询数据库
测试:
-
开启日志
-
测试在一个Session中查询两次相同记录
@Test public void cacheTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); AccountMapper mapper = sqlSession.getMapper(AccountMapper.class); Account account = mapper.getByID(10); System.out.println(account); //如果这中间出现了增删改的操作,缓存会失效 System.out.println("========================="); Account account2 = mapper.getByID(10); System.out.println(account2); System.out.println(account==account2); }
-
查看日志输出
缓存失效的原因:
-
查询内容不同
-
增删改操作
-
查询不同的Mapper.xml
-
手动清理缓存
-
SqlSession.clearCache(); //手动清理缓存
-
小结:一级缓存是默认开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段
二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中;
步骤:
1、开启全局缓存
<!-- 显式地开启全局缓存 -->
<setting name="cacheEnable" value="true"/>
2、在要使用二级缓存的mapper中开启
<!-- 在当前Mapper.xml中使用二级缓存 -->
<cache/>
也可以自定义参数
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
Mybatis的缓存机制
参考链接:https://tech.meituan.com/2018/01/19/mybatis-cache.html
自定义缓存
最后
- 只是单纯地记录下自己的学习过程,如果能或多或少帮到一些跟我一样刚入门Mybatis的小白们,是我最开心的事情
- 该笔记是自己的一些心得且大部分笔记内容基于b站up主:遇见狂神说,并附上链接:【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂_哔哩哔哩_bilibili
- 学习之路漫漫,很多优秀的人尚且都在努力学习,自己有什么理由不坚持下去呢。
END