数据持久化
持久化就是将程序的数据在持久状态和瞬时状态转化的过程内存:断电即失数据库(JDBC),IO文件持久化生活:冷藏、罐头。为什么需要持久化?
有一些对象,不能让他断电即失内存太贵了Dao层、Service层,Controller层…
完成持久化工作的代码块层界限十分明显。搭建数据库
新建项目
新建项目
删除src将项目作为父工程
<?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>org.example</groupId> <artifactId>MybatisStudy</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>mybatis-01</module> </modules> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>编写mybatis核心配置文件
<?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.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="password"/> </dataSource> </environment> </environments> <!--<mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers>--> </configuration>编写mybatis工具类
public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { //获取mybatis第一步,获取SqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }实体类
Dao接口
public interface UserDao { List<User> getUserList(); }接口实现类由原来的UserDaoImpl转变为一个Mapper文件
namespace绑定接口id绑定方法resultType绑定类 <?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"> <!--namespace=绑定一个对应的Dao/Mapper接口--> <mapper namespace="org.why.dao.UserDao"> <select id="getUserList" resultType="org.why.pojo.User"> select * from mybatis.user </select> </mapper>注意点:
org.apache.ibatis.binding.BindingException: Type interface org.why.dao.UserMapper is not known to the MapperRegistry.
MapperRegistry是什么?
核心配置中文注册mappers
当配置文件无法被导出或者生效时,使用以下代码
<!--在build中配置resources,来防止我们资源导出失败的问题--> <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>Junit测试
@Test public void test(){ //获得sqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //方式一:执行SQL UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); for (User user : userList) { System.out.println(user); } sqlSession.close(); }可能遇到的问题:
配置文件没有注册绑定接口错误方法名不对返回类型不对Maven导出资源问题namespace中的包名要和Dao/mapper接口的包名一致!
选择,查询语句:
id:就是对应的namespace中的方法名resultType:SQL语句执行的返回值parameterType:参数类型编写接口
//根据ID查询用户 User getUserById(int id);编写对应的mapper中的sql语句
<select id="getUserById" parameterType="int" resultType="org.why.pojo.User"> select *from mybatis.user where id = #{id} </select>测试
@Test public void getUserById(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUserById(1); System.out.println(user); sqlSession.close(); } 注意点:增删改需要提交事务(sqlSession.admit)
假设我们的实体类或者数据库中的表,字段或者参数过多,我们应当考虑使用Map!
//万能的Map int addUser2(Map<String,Object> map); int updateUser2(Map<String,Object> map); <!--传递map的key,在update中可以只更新想要更新的内容,而不需要全部写出来--> <insert id="addUser2" parameterType="map"> insert into mybatis.user (id,name,pwd) values (#{userId},#{userName},#{password}); </insert> <update id="updateUser2" parameterType="map"> update mybatis.user set pwd=#{password} where id=#{userId} </update> @Test public void addUser2(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String,Object> map = new HashMap<String, Object>(); map.put("userId",3); map.put("userName","why3"); map.put("passWord","333333");//W为大写,所以密码插入不进去 mapper.addUser2(map); sqlSession.commit(); sqlSession.close(); } public void updateUser2(){ SqlSession sqlSession = MybatisU tils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String,Object> map = new HashMap<String, Object>(); map.put("userId",3); map.put("password","233333"); mapper.updateUser2(map); sqlSession.commit(); sqlSession.close(); }Map传递参数,直接在sql中取出key即可!【parameterType=“map”】
对象传递参数,直接在sql中取出对象的属性即可!【parameterType=“Object”】
只有个基本类型参数的情况下,可以直接在sql中取到!
多个参数用Map,或者注解
java代码执行的时候,传递通配符% %
List<User> userList = mapper.getUserLike("%w%");在sql拼接中使用通配符
select * from mybatis.user where name like concat('%',#{value},'%');所有元素都必须按顺序编写!!!
properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) {JDBC / MANAGMENT(了解即可)} dataSource(数据源) {UNPOOLED / POOLED / JNDI} (池:用完可以回收) databaseIdProvider(数据库厂商标识) mappers(映射器)Mybatis可以配置成适应多种环境
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
学会使用配置多套运行环境
Mybatis默认的事务管理器就是JDBC,连接池:POOLED
可以通过properties属性来实现引用配置文件
这些属性可以在外部进行配置,并可以进行动态替换。既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。【db.properties】
db.properties文件
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8 username=root pwd=123456在核心配置文件中引入
<!--引入外部配置文件--> <properties resource="db.properties"> <!--存在相同元素,优先引入外部文件--> <property name="pwd" value="111111"/> </properties> 可以直接引入外部文件可以在其中增加一些配置属性如果两个文件有同一个字段,优先使用外部配置文件的!也可以指定一个包名,Mybatis会在包名下面搜索需要的Java Bean,比如:
扫描实体类的包,它的默认别名就为这个类的类名,首字母小写!
<typeAliases> <package name="org.why.pojo"/> </typeAliases>在实体类比较少的时候,使用第一种方式
如果实体类十分多,建议使用第二种
第一种可以DIY别名,第二种要改别名,需要在实体上增加注解
@Alias("user") public class user{...}注意的几个设置
设置名描述有效值默认值cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetruelazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true | falsefalselogImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置类型处理器(typeHandlers)
对象工厂(objectFactory)
插件(plugins)
mybatis-generator-coremybatis-plus通用mapperMapperRegistry:注册绑定我们的Mapper文件
方式一:
<mappers> <mapper resource="org/why/dao/UserMapper.xml"/> </mappers>方式二:使用class文件绑定注册
<mappers> <mapper class="org.why.dao.UserMapper"/> </mappers>注意点:
接口和他的Mapper配置文件必须同名接口和他的Mapper配置文件必须在同一包下方式三:使用包扫描进行注入绑定
<mappers> <package name="org.why.dao"/> </mappers>注意点:
接口和他的Mapper配置文件必须同名接口和他的Mapper配置文件必须在同一包下作用域,和生命周期,是至关重要的,因为错误的使用会导致非常严重的并发问题。
sqlSessionFactoryBuilder:
一旦创建了sqlSessionFactoryBuilder,就不再需要作为局部变量来创建sqlSessionFactory:
可以理解为:数据库连接池一旦运行就一直存在,没有任何理由丢弃它或者重新创建最佳作用域是应用作用域最简单就是使用单例模式或者静态单例模式sqlSession:
可以理解为:连接到连接池的一个请求不是线程安全,不能被共享最佳作用域是请求或方法作用域用完马上关闭,否则资源占用一个sqlSessionFactory可以创建 多个sqlSession
一个sqlSession可以引用 多个Mapper
每一个Mapper代表一个具体的业务
当实体类字段和数据库中的字段不一致时,会无法查询出该字段数据
解决方法:
起别名
<select id="getUserById" resultType="user"> select id,name,pwd from mybatis.user where id = #{id} </select>resultMap
结果映射集
id name pwd id name password <!--结果集映射--> <resultMap id="UserMap" type="user"> <!--column数据库中的字段,property实体类中的属性--> <result column="id" property="id"/> <result column="name" property="name"/> <result column="pwd" property="password"/> </resultMap> <select id="getUserById" resultMap="UserMap"> select * from mybatis.user where id = #{id} </select> resultMap元素是Mybatis中最重要最强大的元素resultMap的设计思想:对于简单语句不需要配置显示的结果映射,对于复杂的语句只需要描述它们的关系就行了。如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手!
曾经:sout、debug
现在:日志工厂
设置名描述有效值默认值logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J 、 LOG4J 、 LOG4J2 、 JDK_LOGGING 、 COMMONS_LOGGING 、 STDOUT_LOGGING 、 NO_LOGGING未设置 SLF4JLOG4J 【掌握】LOG4J2JDK_LOGGINGCOMMONS_LOGGINGSTDOUT_LOGGING【掌握】NO_LOGGING在mybatis中具体使用哪一个日志,在设置中配置
STDOUT_LOGGING标准日志输出:
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> Opening JDBC Connection Created connection 832279283. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@319b92f3] ==> Preparing: select * from mybatis.user where id = ? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, why1, 123456 <== Total: 1 User{id=1, name='why1', password='123456'} Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@319b92f3] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@319b92f3] Returned connection 832279283 to pool.先导入LOG4J 的包
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>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/why.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配置log4j为日志实现
<settings> <setting name="logImpl" value="LOG4J "/> </settings>Log4j的简单使用
导入包import org.apache.log4j.Logger;
日志对象,参数为当前类的class
static Logger logger= Logger.getLogger(UserMapperTest.class);日志级别
logger.info("info:进入了testLog4j"); logger.debug("debug:进入了testLog4j"); logger.error("error:进入了testLog4j");为什么要分页?
减少数据处理量使用Mybatis实现分页
接口
List<User> getUserByLimit(Map<String,Integer> map);Mapper.xml
<!--分页--> <select id="getUserByLimit" parameterType="map" resultType="user"> select * from mybatis.user limit #{startIndex},#{pageSize} </select>测试
@Test public void getUserByLimit(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Integer> map = new HashMap<>(); map.put("startIndex",0); map.put("pageSize",2); List<User> userList = mapper.getUserByLimit(map); for (User user : userList) { System.out.println(user); } sqlSession.close(); }知道即可
mybatis分页插件PageHelper
了解即可
根本原因:解耦
注解在接口上实现
@Select("select * from user") List<User> getUsers();需要在核心配置文件中绑定接口
<!--绑定接口--> <mappers> <mapper class="org.why.dao.UserMapper"/> </mappers>测试
@Test public void test(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //底层主要应用反射 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.getUsers(); for (User user : users) { System.out.println(user); } sqlSession.close(); }本质:反射机制实现
底层:动态代理
mybatis详细的执行流程!
resources获取加载全局配置文件实例化SqlSessionFactoryBuilder构造器解析文件流XML ConfigBuilderConfiguration所有配置信息sqlSessionFactory实例化transactional事务管理创建**executor**执行器创建sqlSession实现CRUD 无法实现回滚到6查看是否执行成功 无法实现回滚到6提交事务关闭可以在工具类创建的时候自动提交事务!
public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); }【注意:我们必须要将接口注册绑定我们的核心配置文件中】
关于@Param
基本类型和String类型的参数,需要加上引用类型不需要如果只有一个基本类型的话,可以忽略,但建议加上在SQL中引用的就是@Param("")中设定的属性名#{} ${}区别
使用步骤:
安装Lombok插件
导入lombok包
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </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 @Data //无参构造,get,set,tostring,hashcode,equals @AllArgsConstructor //有参构造 @NoArgsConstructor //无参构造回顾MySQL多对一处理方式
子查询联表查询注意点:
保证SQL的可读性,尽量保持通俗易懂注意一对多和多对一中,属性名和字段的问题如果问题不好排查错误,可以使用日志,建议使用Log4j面试高频
mysql引擎InnoDB底层原理索引索引优化动态sq就是根据不同的条件生成不同的SQL语句
ifchoose (when, otherwise)trim (where, set)foreach所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码
有时候,我们会将一些功能提取出来,方便复用
使用SQL标签抽取公共部分
<sql id="if-title-author"> <if test="title != null"> and title=#{title} </if> <if test="author != null"> and author=#{author} </if> </sql>在需要使用的地方用include标签引用即可
select * from mybatis.blog <where> <include refid="if-title-author"></include> </where>注意事项:
最好基于单表来定义SQL片段不要存在where标签动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合即可
【建议:先在MySQL中写出完整的SQL语句,再去修改为动态SQL去实现即可】
查询 --> 连接数据库 --> 耗资源!
一次查询的结果,暂存在一个可以直接取到的地方!–> 内存:缓存
我们再次查询相同数据的时候,直接走缓存,而不用走数据库了
mybatis默认定制了两级缓存:一级缓存和二级缓存
默认情况下,只有一级缓存开启;(sqlsession级别的缓存,也称本地缓存)二级缓存需要手动开启和配置;(namespace级别的缓存,全局缓存)未来提高扩展性,mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存一级缓存也叫本地缓存:SqlSession
与数据库同一次回话期间查询到的数据会放在本地缓存中。以后需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库测试步骤:
开启日志在一个session中查询两次相同记录查看日志输出缓存失效的情况:
查询不同对象进行增删改操作(可能会改变原来的数据)查询不同的Mapper.xml手动清理缓存**小结:**一级缓存默认开启,只在一次SqlSession中有效,也就是拿到连接到关闭连接这一区间段
步骤:
开启全局缓存
<!--显示的开启全局缓存--> <setting name="cacheEnable" value="true"/>在要使用二级缓存的Mapper中开启
<!--在当前Mapper。xml中使用二级缓存--> <cache/>也可以自定义参数
<!--在当前Mapper。xml中使用二级缓存--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>测试
问题:我们需要将实体类序列化!否则报错【readOnly="true"不需要序列化】
Caused by: java.io.NotSerializableException: org.why.pojo.User小结:
只要开启了二级缓存,在同一个Mapper下就有效所有数据都会先放在一级缓存中只有当会话提交,或者关闭的时候,才会提交到二级缓存ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存
要在程序中使用ehcache,先要导包!
在mapper中指定使用ehcache缓存实现
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>