在知道Mybatis(原名ibatis)怎么用以前,对于在代码中链接数据库,我都是用JDBC链接的,例如这样:html
Connection connection = DriverManager.getConnection(jdbcUrl, user, password);
String sqlCommend = "select goods.id,goods.name,sum(`order`.goods_num*goods_price) as gmv from `order` \n" +
"join goods\n" +
"on goods.id = `order`.goods_id\n" +
"group by goods_id \n" +
"order by gmv desc\n" +
"\n";
try (PreparedStatement pS = databaseConnection.prepareStatement(sqlCommend)) {
ResultSet resultSet = pS.executeQuery();
return getGoodsAndGmv(resultSet);
}
复制代码
这样看起来也没多麻烦,可是谁也不想本身的函数中出现这么一段不堪的语句。因此,Mybatis为咱们提供了更加方便的执行数据库操做的方法。
Mybatis自己也是一种ORM(Object Relationship Mapping)框架,既对象关系映射,说白了就是实现数据库到Java对象的一个映射,就是咱们与数据库打交道的一个中间层。
Mybatis的官方文档写的很是详细,你碰到的问题基本上均可以经过官方文档解决。java
跟着官方文档一步步走,首先须要从外部引入Mybatis的jar包,使用Maven的话则须要引入Maven配置,接下来就是配置资源文件了。首先明白,在Java中把非代码的内容都称为资源,包括图片、视频、数据库等,资源目录与代码目录结构相似。git
<?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="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
复制代码
注意:github
<!--数据库的驱动类型-->
<property name="driver" value="org.h2.Driver"/>
<!--数据库的链接串-->
<property name="url" value="jdbc:h2:file:H:/githubitem/SinaCrawler/sina-crawler/SinaCrawler"/>
<!--用户名-->
<property name="username" value="root"/>
<!--密码-->
<property name="password" value="password"/>
复制代码
以及sql
<mappers>
<mapper resource="XXX/XXX/XXX/XXX.xml"/>
<mapper resource="XXX/XXX/XXX/XXX$XXX"/>
</mappers>
复制代码
是须要根据本身的实际状况修改的。这里用的是本身的一个H2数据库为例子,
数据库中的内容为:数据库
用户表:
+----+----------+------+----------+
| ID | NAME | TEL | ADDRESS |
+----+----------+------+----------+
| 1 | zhangsan | tel1 | beijing |
+----+----------+------+----------+
| 2 | lisi | tel2 | shanghai |
+----+----------+------+----------+
| 3 | wangwu | tel3 | shanghai |
+----+----------+------+----------+
| 4 | zhangsan | tel4 | shenzhen |
+----+----------+------+----------+
商品表:
+----+--------+-------+
| ID | NAME | PRICE |
+----+--------+-------+
| 1 | goods1 | 10 |
+----+--------+-------+
| 2 | goods2 | 20 |
+----+--------+-------+
| 3 | goods3 | 30 |
+----+--------+-------+
| 4 | goods4 | 40 |
+----+--------+-------+
| 5 | goods5 | 50 |
+----+--------+-------+
订单表:
+------------+-----------------+------------------+---------------------+-------------------------------+
| ID(订单ID) | USER_ID(用户ID) | GOODS_ID(商品ID) | GOODS_NUM(商品数量) | GOODS_PRICE(下单时的商品单价) |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 1 | 1 | 1 | 5 | 10 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 2 | 2 | 1 | 1 | 10 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 3 | 2 | 1 | 2 | 10 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 4 | 4 | 2 | 4 | 20 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 5 | 4 | 2 | 100 | 20 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 6 | 4 | 3 | 1 | 20 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 7 | 5 | 4 | 1 | 20 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 8 | 5 | 6 | 1 | 60 |
+------------+-----------------+------------------+---------------------+-------------------------------+
复制代码
接下来先用一个简单的例子来说解整个过程:
获取全部的用户信息: 写一个接口:apache
public interface UserMapper{
@Select("select * from user")
List<User> getUsers();
}
复制代码
实现这个接口:编程
public static void main(String[] args) throws IOException {
String resource = "db/mybatis/config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//得到SqlSession实例
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
//生成代理类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user:users
) {
System.out.println(user);
}
}
复制代码
config.xml配置文件中添加这个接口的绝对路径安全
<mappers>
<!-- $区份内部类的分隔符 -->
<mapper class="com.github.hcsp.sql.Sql$UserMapper"/>
</mappers>
复制代码
点击运行就能够看到结果了:bash
User{id=1, name='zhangsan', tel='tel1', address='beijing'}
User{id=2, name='lisi', tel='tel2', address='shanghai'}
User{id=3, name='wangwu', tel='tel3', address='shanghai'}
User{id=4, name='zhangsan', tel='tel4', address='shenzhen'}
Process finished with exit code 0
复制代码
问题来了,咱们并无实现这个接口,那么结果是怎么出来的呢?看到MapperRegistry中的getMapper:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
复制代码
能够看到,这里使用了代理模式,Mybatis识别出了@Select注解,并生成代理类,在代理类中包含接口的实现方法。
再问一个问题:
能够看到,在咱们在数据库中查询数据的结果是这样的,Mybatis是怎么把它转换为User类的呢?
其实这也是经过反射完成的,根据每一列的列名去查找User类中的成员变量,根据查到的行数生成对应个数的对象。
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
复制代码
注意放置的位置:
# Global logging configuration
# 日志等级为DEBUG,标准输出
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
# 标准输出 = 控制台输出源
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
# 标准布局 = log4j模式化布局
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# 转化输出的模式 = 优先级(占5个字节)[线程名]
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
复制代码
再次运行一下试试看:
DEBUG [main] - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
DEBUG [main] - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 2076287037.
DEBUG [main] - Setting autocommit to false on JDBC Connection [conn0: url=jdbc:h2:file:H:/github item/SinaCrawler/sina-crawler/SinaCrawler user=ROOT]
DEBUG [main] - ==> Preparing: select * from user
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 4
User{id=1, name='zhangsan', tel='tel1', address='beijing'}
User{id=2, name='lisi', tel='tel2', address='shanghai'}
User{id=3, name='wangwu', tel='tel3', address='shanghai'}
User{id=4, name='zhangsan', tel='tel4', address='shenzhen'}
DEBUG [main] - Resetting autocommit to true on JDBC Connection [conn0: url=jdbc:h2:file:H:/github item/SinaCrawler/sina-crawler/SinaCrawler user=ROOT]
DEBUG [main] - Closing JDBC Connection [conn0: url=jdbc:h2:file:H:/github item/SinaCrawler/sina-crawler/SinaCrawler user=ROOT]
DEBUG [main] - Returned connection 2076287037 to pool.
Process finished with exit code 0
复制代码
如今就能够看到DEBUG等级及如下的日志信息了。注意:
log4j.properties文件必须直接放在resources目录下,不然系统会找不到该文件。 顺便介绍一下日志等级:
在log4j.jar/org/apache/log4j/Level类中能够看到有关日志等级的声明:
TRACE对程序运行没有影响,既不打印到控制台也不输出到文件,主要用以线上调试,若是须要查看TRACE等级的日志,须要经过elog命令开启TRACE,或者将程序日志输出级别降至TRACE。
默认状况下,打印至终端,可是不归档到日志文件。所以通常用于程序启动时,查看日志流水信息。
INFO等级的日志信息都是一过性的,不会大量反复输出。该级别日志默认状况下会打印到终端和日志文件。
代表程序处理中可能遇到的错误,以及非法数据。该警告是一过性的,可恢复不影响程序进行。
该错误发生后程序任然能够运行,可是极有可能在某种不正常的状况下运行。
错误直接致使程序没法启动,须要当即解决。
Mapper有两种:
详细介绍第二种Mapper。根据官网提示,新建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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
复制代码
namespace-命名空间,本身随便取个名字。 id -给本身的SQL语句取名。
resultType -返回值类型。
示例:
<mapper namespace="com.github.hcsp.sql.Sql">
<select id="selectUsers" resultType="Map">
select id,name,address,tel from User
</select>
复制代码
在主函数中:
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
System.out.println(sqlSession.selectList("com.github.hcsp.sql.Sql.selectUsers"));
}
复制代码
结果:
DEBUG [main] - <== Total: 4
[{ADDRESS=beijing, TEL=tel1, ID=1, NAME=zhangsan}, {ADDRESS=shanghai, TEL=tel2, ID=2, NAME=lisi}, {ADDRESS=shanghai, TEL=tel3, ID=3, NAME=wangwu}, {ADDRESS=shenzhen, TEL=tel4, ID=4, NAME=zhangsan}]
复制代码
返回值类型:
前面的实例中,咱们的返回值类型为Map,因此返回的是键值对。那么,咱们咱们还能够新建一个类来存放结果。 像这样
public class User {
Integer id;
String name;
String address;
String tel;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' + ", address='" + address + '\'' + ", tel='" + tel + '\'' +
'}';
}
}
复制代码
返回值类型为User的全限定类名
<select id="selectUsers" resultType="com.github.hcsp.sql.User">
复制代码
结果以下:
[User{id=1, name='zhangsan', address='beijing', tel='tel1'}, User{id=2, name='lisi', address='shanghai', tel='tel2'}, User{id=3, name='wangwu', address='shanghai', tel='tel3'}, User{id=4, name='zhangsan', address='shenzhen', tel='tel4'}]
复制代码
能够看到返回值就变成一个个的User了,读写参数都是遵照JavaBean约定使用getter()和setter()进行的。
注意:可使用类型别名,简化resultType。假若有几十个方法的返回值类型都是User类型,每次都去写全限定类型实在是麻烦,并且包名不能动,不然返回值类型全都要动,因此设置类型别名颇有必要。在config.xml中添加如下内容:
<typeAliases>
<typeAlias alias="User" type="com.github.hcsp.sql.Sql.User"/>
</typeAliases>
复制代码
注意添加顺序,那么返回值类型能够直接写:
<select id="selectUsers" resultType="User">
复制代码
其实Map也是全限定类名java.lang.HashMap的简写。
传入参数:
查找id为1的用户,能够看到:
<E> List<E> selectList(String statement, Object parameter);
复制代码
selectList还有一个带parameter的多态方法,对于一个参数的SQL语句
select id,name,address,tel from User where id=#{id}
复制代码
直接往里面塞一个参数便可:
sqlSession.selectList("com.github.hcsp.sql.Sql.selectUsers",1)
复制代码
若是有多个参数就放一个类进去:
User user = new User();
user.id=1;
user.name = "zhangsan";
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
System.out.println(sqlSession.selectList("com.github.hcsp.sql.Sql.selectUsers",user));
}
复制代码
设置参数还可使用${},两者区别在于${}只是简单的替换,而#{}是防注入的替换。
演示一下SQL注入: 以这条语句为例:
select id,name,address,tel from User where name='${name}' and id=${id}
复制代码
我传入这样一个User:
User user = new User();
user.name = "'or 1=1--";
复制代码
结果以下:
DEBUG [main] - ==> Preparing: select id,name,address,tel from User where name=''or 1=1--' and id= DEBUG [main] - ==> Parameters: DEBUG [main] - <== Total: 4 [User{id=1, name='zhangsan', tel='tel1', address='beijing'}, User{id=2, name='lisi', tel='tel2', address='shanghai'}, User{id=3, name='wangwu', tel='tel3', address='shanghai'}, User{id=4, name='zhangsan', tel='tel4', address='shenzhen'}] 复制代码
能够看到,我拿到了数据库中的全部内容。因此,${}是不安全的传参数的方法。
固然,除了每次都新建一个User对象这种耗费内存的方法以外,还能够用Map:
Map<Object,Object> map = new HashMap<>();
map.put("name","zhangsan");
map.put("id",1);
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
System.out.println(sqlSession.selectList("com.github.hcsp.sql.Sql.selectUsers",map));
}
复制代码
文字部分Mybatis官网上有说明,也有例子,本身写两个加深下理解。
<select id="selectUsers" resultType="User">
select id,name,address,tel from User where name='${name}'
<if test="id !=null">
and id=${id}
</if>
</select>
复制代码
这里注意,不要把where、and这种语句写在if判断外面,不然像
select * from user where
这种语句是不符合格式要求的,就会报错。
<select id="selectUser" resultType="User">
select * from User
<choose>
<when test="name==zhangsan">
where name = 'zhangsan'
</when>
<otherwise>
where name = 'lisi'
</otherwise>
</choose>
</select>
复制代码
第一次
map.put("name","lisi");
获得的是select * from User where name = 'lisi'
,这没问题,第二次map.put("name","zhangsan");
,获得的仍是select * from User where name = 'lisi'
。这就蹊跷了,其实缘由在于<when test="name==zhangsan">
,没有把zhangsan用``包起来,Mybatis误觉得zhangsan也是变量,等着你去传值,而后将name传入的`zhangsan`与null进行比较。全部不管后面参数怎么传,sql语句都不会按照你所想的逻辑去执行。同时这也证实了一点,name、zhangsan这种参数的初始值都是null。
where、trim、set
这个没什么好讲的,直接看官网的例子一看就明白了。
foreach-实现批量更新SQL
先来个简单的:
找出id在某个集合中的User
<select id="selectIdIn" resultType="User">
SELECT *
FROM User
WHERE id in
<!-- item/index-占位符,collection-须要从哪一个集合中找出结果-->
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
复制代码
map.put("list",Arrays.asList(1,2,3,5,6));
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
System.out.println(sqlSession.selectList("com.github.hcsp.sql.Sql.selectIdIn",map));
}
复制代码
结果以下:
DEBUG [main] - ==> Preparing: SELECT * FROM User WHERE id in ( ? , ? , ? , ? , ? )
DEBUG [main] - ==> Parameters: 1(Integer), 2(Integer), 3(Integer), 5(Integer), 6(Integer)
DEBUG [main] - <== Total: 3
[User{id=1, name='zhangsan', tel='tel1', address='beijing'}, User{id=2, name='lisi', tel='tel2', address='shanghai'}, User{id=3, name='wangwu', tel='tel3', address='shanghai'}]
复制代码
foreach帮咱们拼接了一个查找id在某个范围内的sql语句。
批量向表中插入user:
SQL语句以下:
<insert id="batchInsertUsers" parameterType="map">
insert into User(id,name,tel,address)
values
<foreach item="user" collection="users" separator=",">
(#{user.id},#{user.name},#{user.tel},#{user.address})
</foreach>
</insert>
复制代码
其实这样看来Mybatis中的foreach与咱们平时写的foreach语句很相似,user是迭代的项目,users是被迭代的集合,中间须要逗号链接。
DEBUG [main] - ==> Preparing: insert into User(id,name,tel,address) values (?,?,?,?) , (?,?,?,?)
DEBUG [main] - ==> Parameters: null, abcd(String), tel-abcd-1(String), addr-abcd(String), null, abcd(String), tel-abcd-2(String), addr-abcd(String)
复制代码
能够看到Mybatis帮咱们拼出了批量插入数据的语句。顺便说一句parameterType不是必要的。
查询订单信息,只查询用户名、商品名齐全的订单,即INNER JOIN方式
能够看到:
public class Order {
private Integer id;
/** 订单中的用户 */
private User user;
/** 订单中的商品 */
private Goods goods;
/** 订单中的总成交金额 */
private BigDecimal totalPrice;
复制代码
在order
表中嵌套了user
跟goods
,那么Order的id能够直接得到,如何把从order表中获取到的结果,赋值给User、Goods类,这就须要使用Mybatis里面的association嵌套了。
<select id="getInnerJoinOrders" resultMap="order">
select `order`.id as order_id,
user.name as user_name,
goods.name as goods_name,
`order`.goods_num as goods_num,
goods.price as goods_price,
`order`.goods_num * `order`.goods_price as total_price
from `order`
inner join goods on goods.id = `order`.goods_id
inner join user on user.id = `order`.user_id
</select>
<resultMap id="order" type="Order">
<result property="id" column="order_id"/>
<result property="totalPrice" column="total_price"/>
<association property="user" javaType="User">
<result property="name" column="user_name"/>
</association>
<association property="goods" javaType="Goods">
<result property="name" column="goods_name"/>
<result property="price" column="goods_price"/>
</association>
</resultMap>
复制代码
能够看到,对于这种状况,咱们不能直接返回一个确切的resultType,而是返回一个结果映射resultMap,也就是在这个例子中,咱们的查询结果order会被映射为另外一个对象。