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"><!--Config约束--> <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?characterEncoding=utf-8" /> <!--所要连接的数据库名--> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
映射文件 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约束--> <mapper namespace="org.mybatis.example.BlogMapper"> <select id="selectBlog" resultType="Blog"> select * from Blog where id = #{id} </select> </mapper>
log4j.properties文件:mybatis默认使用log4j作为输出日志信息。
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
#Consoleoutput...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
1. 配置
configuration(配置)
-
properties(属性)
-
settings(设置))
-
typeAliases(类型别名)
-
typeHandlers(类型处理器)
-
objectFactory(对象工厂)
-
plugins(插件)
-
environments(环境配置)
-
environment(环境变量)
-
transactionManager(事务管理器)
-
dataSource(数据源)
-
-
-
databaseIdProvider(数据库厂商标识)
-
mappers(映射器
2.属性(properties)
这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递
-
在配置文件中的配置
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中 使用相同的 SQL 映射。有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:
-
每个数据库对应一个 SqlSessionFactory 实例
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:
-
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
-
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
-
默认使用的环境 ID(比如:default="development")。
-
每个 environment 元素定义的环境 ID(比如:id="development")。
-
事务管理器的配置(比如:type="JDBC")。
-
数据源的配置(比如:type="POOLED")。
默认的环境和环境 ID 是自解释的,因此一目了然。 你可以对环境随意命名,但一定要保证默认的环境 ID 要匹配其中一个环境 ID。
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type=”[JDBC|MANAGED]”):
-
JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
-
MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。例如:
-
<transactionManager type="MANAGED"> <property name="closeConnection" value="false"/> </transactionManager>
提示:如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器, 因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
-
许多 MyBatis 的应用程序会按示例中的例子来配置数据源。虽然这是可选的,但为了使用延迟加载,数据源是必须配置的。
有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]”):
UNPOOLED
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
JNDI – 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用
详细解释请到mybatis官网
<properties resource="jdbc.properties"> <!-- 在properties内部用property定义属性 --> <!-- 如果外部配置文件有该属性,则内部定义属性被外部属性覆盖 --> <property name="jdbc.username" value="root" /> <property name="jdbc.password" value="root" /> </properties>
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC jdbc.username=root jdbc.password=root
注意: MyBatis 将按照下面的顺序来加载属性:
-
在 properties 元素体内定义的属性首先被读取。
-
然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。
3.mapper(映射器)
定义 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> <!-- 使用完全限定资源定位符(URL) --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/> </mappers> <!-- 使用映射器接口实现类的完全限定类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers> <!-- 将包内的映射器接口实现全部注册为映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
类似的其他标签可在mybatis官网查询
创建实体类
数据库中的字段定义
加载映射文件
mybatis框架需要加载Mapper.xml映射文件
将users.xml添加在SqlMapConfig.xml,如下:
简单的增删改查
1. 查
根据用户id查询用户
1.1映射文件
<?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"> <!-- 写Sql语句 命名空间 --> <mapper namespace="test"> <!-- 通过ID查询一个用户,resultType返回类型为自动,值为实体类的全类路径,里面定义数据要与数据库中的字段一致--> <select id="findUserById" parameterType="Integer" resultType="com.mybatis.pojo.User"> select * from user where id = #{v} </select>
@Test public void testMybatis() throws Exception { //加载核心配置文件 String resource = "SqlMapConfig.xml"; InputStream in = Resources.getResourceAsStream(resource); //创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in); //创建SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //执行Sql语句 User user = sqlSession.selectOne("test.findUserById", 26); System.out.println(user); }
2.增
2.1映射文件
@Test public void testInsertUser() throws Exception { //加载核心配置文件 String resource = "sqlMapConfig.xml"; InputStream in = Resources.getResourceAsStream(resource); //创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in); //创建SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //执行Sql语句 User user = new User(); user.setUsername("广州"); user.setBirthday(new Date()); user.setAddress("广州市"); user.setSex("男"); int i = sqlSession.insert("test.insertUser", user); //插入数据 sqlSession.commit(); //执行 // 提交事务 增删改 需要commit,查询无需commit System.out.println(user.getId());}
3.改
3.1映射文件
3.2测试程序
4.删
4.1映射文件
小结
1. #{}和${}
#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换。#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
${}表示拼接sql串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。
2. parameterType和resultType
parameterType:指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。
resultType:指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。如果有多条数据,则分别进行映射,并把对象放到容器List中
3. selectOne和selectList
selectOne查询一条记录,如果使用selectOne查询多条记录则抛出异常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70)
selectList可以查询一条或多条记录。
Dao开发方法
1.SqlSession原理和使用范围
原理:SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法,但是新版的Mybatis中就会建议使用Mapper接口的方法。
映射器其实就是一个动态代理对象,进入到MapperMethod的execute方法就能简单找到SqlSession的删除、更新、查询、选择方法,从底层实现来说:通过动态代理技术,让接口跑起来,之后采用命令模式,最后还是采用了SqlSession的接口方法(getMapper()方法等到Mapper)执行SQL查询(也就是说Mapper接口方法的实现底层还是采用SqlSession接口方法实现的)。
使用范围:SqlSession中封装了对数据库的操作,如:查询、插入、更新、删除等。
SqlSession通过SqlSessionFactory创建。
SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。
1.1SqlSessionFactoryBuilder
SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory创建的。所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。
1.2SqlSessionFactory
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。
1.3SqlSession
SqlSession是一个面向用户的接口,sqlSession中定义了数据库操作方法。
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。如下:
SqlSession session = sqlSessionFactory.openSession(); try { // do work } finally { session.close(); }
2.原始Dao开发方式
原始Dao开发方法需要程序员编写Dao接口和Dao实现类
2.1映射文件
编写映射文件如下:
<?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"> <!-- 写Sql语句 命名空间 --> <mapper namespace="test"> <!--通过ID查询一个用户,resultType返回类型为自动,值为实体类的全类路径,里面定义数据要与数据库中的字段一致--> <select id="findUserById" parameterType="Integer" resultType="com.mybatis.pojo.User"> select * from user where id = #{v} </select> <select id="findUserByUsername" parameterType="String" resultType="com.mybatis.pojo.User"> select * from user where username like "%"#{haha}"%" </select> <!-- 添加用户 --> <insert id="insertUser" parameterType="com.mybatis.pojo.User"> <selectKey keyProperty="id" resultType="Integer" order="AFTER"> <!--执行完保存数据并自动生成id--> select LAST_INSERT_ID() </selectKey> insert into user (username,birthday,address,sex) values (#{username},#{birthday},#{address},#{sex}) </insert> <!-- 更新 --> <update id="updateUserById" parameterType="com.mybatis.pojo.User"> update user set username = #{username},sex = #{sex},birthday = #{birthday},address = #{address} where id = #{id} </update> <!-- 删除 --> <delete id="deleteUserById" parameterType="Integer"> delete from user where id = #{vvvvv} </delete> </mapper>
2.5问题
原始Dao开发中存在以下问题:
Dao方法体存在重复代码:通过SqlSessionFactory创建SqlSession,调用SqlSession的数据库操作方法
调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不得于开发维护。
3.Mapperd动态代理方式
3.1开发规范
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper接口开发需要遵循以下规范:
-
Mapper.xml文件中的namespace与mapper接口的类路径相同。
-
Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
-
Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
-
Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
3.2Mapper.xml(映射文件)
3.6小结
selectOne和selectList
动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口方法的返回值决定,如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。
namespace
mybatis官方推荐使用代理方法开发接口,程序员不用编写接口实现类,使用代理方法时,输入参数可以使用包装对象或对象,保证的通用性。
在连接数据库时出现的错误
-
Loading class
com.mysql.jdbc.Driver'. This is deprecated. The new driver class is
com.mysql.cj.jdb异常错误:Loading class
com.mysql.jdbc.Driver'. This is deprecated. The new driver class is
com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.这个问题主要用了最新的mysql连接驱动
以前的连接驱动版本为:
<property name="driver" value="com.mysql.jdbc.Driver" />
则便解决
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
连接MySQL数据库时报以下时区错误信息:Error querying database. Cause: java.sql.SQLException: The server time zone value '�й���ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
原本为:
<property name="url" value="jdbc:mysql://localhost:3306/mybatischaracterEncoding=utf8" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8&useUnicode=true&sessionVariables=storage_engine%3DInnoDB&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC" />
但这将会报出新错误:
org.apache.ibatis.exceptions.PersistenceException:
Error querying database. Cause: java.sql.SQLException: Unknown system variable 'storage_engine'
原因:MySQL 5.7.x 默认使用 InnoDB,并删除了系统变量。具有此变量会导致 Confluence 无法启动。storage_engine
-
Stop Confluence
-
编辑
<confluence-home>/confluence.cfg.xml
<confluence_home> 目录是以下文件中定义的路径:
<confluence_install>/confluence/WEB-INF/classes/confluence-init.properties
-
查找类似于:
<property name="hibernate.connection.url">jdbc:mysql://localhost/confluence?sessionVariables=storage_engine%3DInnoDB&amp;useUnicode=true&amp;characterEncoding=utf8</property>
4.
<property name="hibernate.connection.url">jdbc:mysql://localhost/confluence?sessionVariables=useUnicode=true&amp;characterEncoding=utf8</property>
5.Start Confluence
即删除上面value中的: storage_engine%3DInnoDB&
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC" />
JUnit4 中@AfterClass @BeforeClass @after @before的区别对比
JUnit4使用Java5中的注解(annotation),以下是JUnit4常用的几个annotation: @Before:初始化方法 对于每一个测试方法都要执行一次(注意与BeforeClass区别,后者是对于所有方法执行一次)
@After:释放资源 对于每一个测试方法都要执行一次(注意与AfterClass区别,后者是对于所有方法执行一次)@Test:测试方法,在这里可以测试期望异常和超时时间
@Test(expected=ArithmeticException.class)检查被测方法是否抛出ArithmeticException异常 @Ignore:忽略的测试方法
@BeforeClass:针对所有测试,只执行一次,且必须为static void
@AfterClass:针对所有测试,只执行一次,且必须为static void 一个JUnit4的单元测试用例执行
-
@BeforeClass – 表示在类中的任意public static void方法执行之前执行
-
@AfterClass – 表示在类中的任意public static void方法执行之后执行
-
@Before – 表示在任意使用@Test注解标注的public void方法执行之前执行
-
@After – 表示在任意使用@Test注解标注的public void方法执行之后执行
-
@Test – 使用该注解标注的public void方法会表示为一个测试方法
顺序为: @BeforeClass -> @Before -> @Test -> @After -> @AfterClass; 每一个测试方法的调用顺序为: @Before -> @Test -> @After;
public class JUnit4Test { @Before public void before() { System.out.println("@Before"); } @Test /** *Mark your test cases with @Test annotations. *You don’t need to prefix your test cases with “test”. *tested class does not need to extend from “TestCase” class. */ public void test() { System.out.println("@Test"); assertEquals(5 + 5, 10); } @Ignore @Test public void testIgnore() { System.out.println("@Ignore"); } @Test(timeout = 50) public void testTimeout() { System.out.println("@Test(timeout = 50)"); assertEquals(5 + 5, 10); } @Test(expected = ArithmeticException.class) public void testExpected() { System.out.println("@Test(expected = Exception.class)"); throw new ArithmeticException(); } @After public void after() { System.out.println("@After"); } @BeforeClass public static void beforeClass() { System.out.println("@BeforeClass"); } @AfterClass public static void afterClass() { System.out.println("@AfterClass"); } }
@Before and @After | |
---|---|
在一个类中只可以出现一次 | 在一个类中可以出现多次,即可以在多个方法的声明前加上这两个Annotaion标签,执行顺序不确定 |
方法名不做限制 | 方法名不做限制 |
在类中只运行一次 | 在每个测试方法之前或者之后都会运行一次 |
@BeforeClass父类中标识了该Annotation的方法将会先于当前类中标识了该Annotation的方法执行。 @AfterClass 父类中标识了该Annotation的方法将会在当前类中标识了该Annotation的方法之后执行 | @Before父类中标识了该Annotation的方法将会先于当前类中标识了该Annotation的方法执行。 @After父类中标识了该Annotation的方法将会在当前类中标识了该Annotation的方法之后执行 |
必须声明为public static | 必须声明为public 并且非static |
所有标识为@AfterClass的方法都一定会被执行,即使在标识为@BeforeClass的方法抛出异常的的情况下也一样会。 | 所有标识为@After 的方法都一定会被执行,即使在标识为 @Before 或者 @Test 的方法抛出异常的的情况下也一样会。 |
@BeforeClass 和 @AfterClass 对于那些比较“昂贵”的资源的分配或者释放来说是很有效的,因为他们只会在类中被执行一次。相比之下对于那些需要在每次运行之前都要初始化或者在运行之后 都需要被清理的资源来说使用@Before和@After同样是一个比较明智的选择。
异常:使用@Before和@Test中,@Before没有执行初始化
代码:
public class MybatisDaoTest { public SqlSessionFactory sqlSessionFactory; @Before public void before() throws Exception { //加载核心配置文件 String resource = "sqlMapConfig.xml"; InputStream in = Resources.getResourceAsStream(resource); //创建SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in); } @Test public void testDao() throws Exception { UserDao userDao = new UserDaoImpl(sqlSessionFactory); User user = userDao.selectUserById(10); System.out.println(user); } }
解决:换成这个包org.junit.Test;
因为idea默认不扫描src路径下的资源文件
在<build><build/>中加入下面的代码
<!-- 如果不添加此节点mybatis的mapper.xml文件都会被漏掉。 --> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.yml</include> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.yml</include> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource>