Mybatis一对一查询与插入

本文详细介绍使用MyBatis处理数据库中的一对一关系,包括插入和查询操作的具体实现方式,探讨了实体类构造方法的重要性及如何利用MyBatis的selectKey特性获取主键。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在搭一个SSM的开发环境给新项目用,就写了一些测试代码,也算温习下自己对Mybatis的学习,而且这一路上还是有很多坑的。

一.Mybatis一对一插入

场景,描述NBA湖人队中队伍,教练和队员的关系
很明显,队伍和教练是一对一的关系,而队伍和队员是一对多的关系,我们先看一对一的关系。

create table coach(
 name varchar2(60),
       id number(10),
       age number(3),
       salary number(10,1),
       primary key(id)

);

create table team(
       team_name varchar2(60),
       location varchar2(60),
       id number(10),
       coach_id number(10),
       all_salary number(11,1),
       primary key(id)

);

team表里面有一个coach_id字段,但是这里并没有设置成外键,因为现在的应用都趋向于去外键,在程序中用代码来控制和确保一致性,这是为了减轻数据库的压力,而把压力放到应用服务器端,不过当然在程序中控制一致性肯定没有数据库控制的好(虽然说数据库其实也是程序呀),但是在不是那么强力的要求强一致性的情况下,还是可以选择不要外键。

package com.wangcc.ssm.entity;

import java.io.Serializable;

public class Coach implements
Serializable{
    private String name;
    private Integer id;
    private int age;
    public Coach() {}
    public Coach(String name, int age, float salary) {
        super();
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    private float salary;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public float getSalary() {
        return salary;
    }
    public void setSalary(float salary) {
        this.salary = salary;
    }

}
package com.wangcc.ssm.entity;

import java.io.Serializable;
import java.util.List;

public class Team implements Serializable{
    private String teamName;
    private String location;
    private Integer id;
    private Coach coach;
    private List<Player> players;
    public List<Player> getPlayers() {
        return players;
    }


    public void setPlayers(List<Player> players) {
        this.players = players;
    }
    public Team () {}

    public Team(String teamName, String location, Coach coach, List<Player> players, float allSalary) {
        super();
        this.teamName = teamName;
        this.location = location;
        this.coach = coach;
        this.players = players;
        this.allSalary = allSalary;
    }


    public Team(String teamName, String location, float allSalary,Coach coach) {
        super();
        this.teamName = teamName;
        this.location = location;
        this.allSalary = allSalary;
        this.coach=coach;
    }


    public Coach getCoach() {
        return coach;
    }
    public void setCoach(Coach coach) {
        this.coach = coach;
    }
    private float allSalary;
    public String getTeamName() {
        return teamName;
    }
    public void setTeamName(String teamName) {
        this.teamName = teamName;
    }
    public String getLocation() {
        return location;
    }
    public void setLocation(String location) {
        this.location = location;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }


    public float getAllSalary() {
        return allSalary;
    }
    public void setAllSalary(float allSalary) {
        this.allSalary = allSalary;
    }

}

这里的实体类我都写了好几个构造方法,其实目的只是为了测试的时候更方便点而已(主要是测试TypeHandler的时候),一般情况下是不需要的,这里细心的同学会发现,我显式的写出了实体类的空构造方法,这是为什么呢,后面会讲到。
对应的Mapper接口和Mapper文件,Mapper接口和Mapper文件中的namespace需要相同,这样才能在XML配置文件中为动态代理过程中找到相应的方法实际执行的sql语句,以及去将sql补全,替换参数注入等工作。

<?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="com.wangcc.ssm.dao.CoachDao">
<select id="getCoachById" parameterType="Integer" resultType="Coach">
select * from coach where id=#{id}
</select>
<insert id="insertCoach" parameterType="Coach">
<!--
AFTER代表在insert语句之后执行
这里我之前一直犯了一个错误
这是把主键返回给JavaBean中的相应属性,为keyProperty对应的属性值,这里为id,而不是说这个方法的返回值就是主键
 Oracle AFTER BEFORE都可以,有小伙伴说必须用BEFORE,但是我试了BEFORE和AFTER都可以 -->
<selectKey resultType="integer" order="AFTER"  keyProperty="id">
select test_coach.currval from dual
</selectKey>
insert into coach (id,name,age,salary) values(test_coach.nextval,#{name},#{age},#{salary})
</insert>
</mapper>


package com.wangcc.ssm.dao;

import com.wangcc.ssm.entity.Coach;

public interface CoachDao {
    public Coach getCoachById(Integer id);
    public Integer insertCoach(Coach coach);
}
<?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="com.wangcc.ssm.dao.TeamDao">

<resultMap type="Team" id="TeamMap">
<result property="teamName" column="team_name" />
<result property="loaction" column="loaction" />
<result property="id" column="id"/>
<result property="allSalary" column="all_salary"/>
<!--  association 和 collection 有顺序要求-->
<association property="coach" column="coach_id" select="com.wangcc.ssm.dao.CoachDao.getCoachById"></association>
<!-- ofType的作用 还有javaType   这里最好还是用javaType="arraylist"-->
<collection property="players" ofType="Player" column="id" select="com.wangcc.ssm.dao.PlayerDao.selectByTeamId">
</collection>
</resultMap>
<select id="getTeamById" parameterType="Integer" resultMap="TeamMap">
select * from team where id=#{id}
</select>
<insert id="insertTeam" parameterType="Team">
<selectKey resultType="integer" order="AFTER" keyProperty="id">
select test_team.currval from dual
</selectKey>
insert into team (id,team_name,location,all_salary,coach_id) values (test_team.nextval,#{teamName},#{location},#{allSalary},#{coach.id})
</insert>
</mapper>
package com.wangcc.ssm.dao;

import com.wangcc.ssm.entity.Team;

public interface TeamDao {
    public Team getTeamById(Integer id);
    public void insertTeam(Team team);
}

我们先看一对一的插入,这里我们想先插入Coach的数据,再将coachId得到,然后再与Team一同插入数据库。
首先,我们知道coachId作为主键,我们要么通过UUID得到,要么就是数据库自增长,当然还有其他方式。也就是说很大的可能,coachId在我们的Java程序中不是由我们自己得到值然后去插入的,而是数据库自动插入,不需要Java程序去提供数据。那么这个时候我们怎么才能得到我们要的coachId呢,Mybatis有一个很好的功能支持了这一想法的实现:selectKey。我们看CoachMapper.xml

<insert id="insertCoach" parameterType="Coach">
<!--
AFTER代表在insert语句之后执行
这里我之前一直犯了一个错误
这是把主键返回给JavaBean中的相应属性,为keyProperty对应的属性值,这里为id,而不是说这个方法的返回值就是主键
 Oracle AFTER BEFORE都可以,有小伙伴说必须用BEFORE,但是我试了BEFORE和AFTER都可以 -->
<selectKey resultType="integer" order="AFTER"  keyProperty="id">
select test_coach.currval from dual
</selectKey>
insert into coach (id,name,age,salary) values(test_coach.nextval,#{name},#{age},#{salary})
</insert>

selectKey用于insert中,他的作用是把主键返回给JavaBean中的相应属性,相应属性对应为keyProperty对应的属性值,需要指定resultType,然而insert中是不能有resultType的,默认为int(rowcount),但是所有的select都必须有result的。selectKey中的order属性有AFTER和BEFORE,代表在insert操作之后和之前进行。我这里用的是Oracle数据库(最近要切换数据库了,得用mysql啦)。网上有人说,用Oracle必须指定order为BEFORE,然而我经过实验,发现前后都可以。Oracle一般用sequence序列做主键,达到自增长效果。其实如果说是只能AFTER的话,我倒是觉得还有点道理,因为对Oracle数据而言,在一个session中,必须,先执行test_coach.nextval,再执行test_coach.currval,否则是会报错的。不能直接执行test_coach.currval,这里为什么可以使用BEFORE呢,难道意思就是每一条语句对应一个Session吗,这个我们调试源码就能知道了。
好,现在我们通过selectkey的使用得到了coachId。
这样我们就可以进行对Team的插入了。
我们看Team的实体类,我们发现,虽然我们插入数据库的coachId,但是实体类中并没有coachId属性,而是有一个Coach属性。那是怎么将coachId插入数据库的呢,我们看xml配置文件。

<insert id="insertTeam" parameterType="Team">
<selectKey resultType="integer" order="AFTER" keyProperty="id">
select test_team.currval from dual
</selectKey>
insert into team (id,team_name,location,all_salary,coach_id) values (test_team.nextval,#{teamName},#{location},#{allSalary},#{coach.id})
</insert>

我们发现coach_id对应了coach.id,我们只需要把coach赋值给Team就可以了,Mybatis会通过反射在coach.id读取出相应的值插入。
我们看相关测试代码。

package com.wangcc.test;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.alibaba.fastjson.JSON;
import com.wangcc.ssm.entity.Coach;
import com.wangcc.ssm.entity.Player;
import com.wangcc.ssm.entity.Team;
import com.wangcc.ssm.service.CoachService;
import com.wangcc.ssm.service.PlayerService;
import com.wangcc.ssm.service.TeamService;
//不支持junit4.4,需要更高版本的junit
//http://blog.csdn.net/zacry/article/details/37052973  http://blog.csdn.net/bruce128/article/details/9792283
@RunWith(SpringJUnit4ClassRunner.class)     //表示继承了SpringJUnit4ClassRunner类  
@ContextConfiguration(locations = {"classpath:mybatis-spring.xml"})  
public class SpringMybatisTest {
     private static Logger logger = Logger.getLogger(SpringMybatisTest.class);  
        @Resource  
        private PlayerService playerService = null;  
        @Resource
        private CoachService coachService;
        @Resource
        private TeamService teamService;
        @Test  
        public void test1() {  
            Player player=new Player(24,14);
            playerService.insert(player);
            // System.out.println(user.getUserName());  
            // logger.info("值:"+user.getUserName());  
            logger.info(JSON.toJSONString(player));  
        }  
        @Test
        public void testGet() {
            Player player=playerService.selectById(24);
            if(player.getName()!=null&&player.getName().equals("")) {
                System.out.println("SUCCESS!");
            }
        }
        @Test
        public void testGetInt() {
            Player player=playerService.selectById(14);
            System.out.println(player.getAge());
        }

        @Test
        public void testOneToOne() {
            Coach coach=new Coach("phil jackson", 79, 11111.1f);
            coachService.insertCoach(coach);
            System.out.println(coach.getId());
            Team team=new Team("lakerss", "los angeles", 1112334.1f, coach);
            teamService.insertTeam(team);

        }
        @Test
        public void testGetOneToOne() {
            Team team=teamService.getTeamById(5);
            logger.info(JSON.toJSONString(team));  

            Coach coach=team.getCoach();
            logger.info(JSON.toJSONString(coach));  
        }
        @Test
        public void testInsertTeam() {
            Coach coach=new Coach("phil jackson1", 80, 11111.1f);
            coachService.insertCoach(coach);
            logger.info(JSON.toJSONString(coach));  
            Team team=new Team("my lakerss1", "los angeles", 1112334.1f, coach);
            teamService.insertTeam(team);
            logger.info(JSON.toJSONString(team));  
            Player player=new Player("kobe paul", 32, team.getId());
            playerService.insert(player);
            Player player1=new Player("paul", 32, team.getId());
            playerService.insert(player1);



        }


        @Test
        public void testGetTeam() {
            Team team=teamService.getTeamById(7);
            logger.info(JSON.toJSONString(team));  
            Coach coach=team.getCoach();
            logger.info(JSON.toJSONString(coach)); 
            List<Player> players=team.getPlayers();
            logger.info(JSON.toJSONString(players));
        }
}
        @Test
        public void testOneToOne() {
            Coach coach=new Coach("phil jackson", 79, 11111.1f);
            coachService.insertCoach(coach);
            System.out.println(coach.getId());
            Team team=new Team("lakerss", "los angeles", 1112334.1f, coach);
            teamService.insertTeam(team);

        }

这个方法就是一对一插入的测试方法。
我们看这个测试类,还是有一些地方可以扯一下的。
刚开始我在pom文件配置的junit的版本是4.4,然后发现@RunWith(SpringJUnit4ClassRunner.class) 报错,查找资料发现是SpringJUnit4ClassRunner不支持4.4junit版本,你需要用更新的版本,然后将junit依然文件version改为4.12。
@ContextConfiguration注解,目前也没有去深究他,看样子是在Junit框架中帮我们完成了自动加载相关配置文件的功能。(相关配置文件上一篇TypeHandler中有)
这里Service的注解不是@Autowired自动注入,而是@Resource。
这两种有什么异同呢,简单的说,他们两个都可以要来装入bean,也都可以用在属性或者setter方法上。
不同的地方呢:
- @Autowired是属于Spring的注解,而且是按照类型匹配的,也就是说当如果你是在对接口使用这个注解的时候,如果他只有一个实现类那还好说,直接用这个注解就好,反正也就一个这种类型的Bean存在,但是有多个实现类的话,这个注解就要打出gg了,因为这个注解是没有name属性滴,他只有一个属性,那就是required,默认为true(必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false),但是Spring这么牛逼的框架不允许这种低级的GG出现,这个时候虽然@Autowired一个人是hold不住了,但是我可以给他一个辅助选手呀,那就是@Qualifier用来指定名称,这个时候相应的接口实现类就不是一个简单的在类上用一个@Service注解或者@Repository就可以了,这个时候就必须要在这些注解上写上对应的name,要不然无法匹配。

  • @Resource是属于Java EE的注解,这个注解和@Autowired不同,认安照名称进行装配,名称可以通过name属性进行指定, 如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
二.一对一查询

看完了一对一插入,我们再来看看一对一查询。
我们来看看TeamMapper.xml的相关配置文件

<resultMap type="Team" id="TeamMap">
<result property="teamName" column="team_name" />
<result property="loaction" column="loaction" />
<result property="id" column="id"/>
<result property="allSalary" column="all_salary"/>
<!--  association 和 collection 有顺序要求-->
<association property="coach" column="coach_id" select="com.wangcc.ssm.dao.CoachDao.getCoachById"></association>
<!-- ofType的作用 还有javaType   这里最好还是用javaType="arraylist"-->
<collection property="players" ofType="Player" column="id" select="com.wangcc.ssm.dao.PlayerDao.selectByTeamId">
</collection>
</resultMap>
<select id="getTeamById" parameterType="Integer" resultMap="TeamMap">
select * from team where id=#{id}
</select>

我们看到这段代码中有resultMap的出现,而CoachMapper.xml中并没有resultMap出现,是上面造成了这个区别呢。
其实并不是CoachMapper.xml没有resultMap,在CoachMapper.XML中我们的select属性中是resultType,其实CoachMapper.XML是隐式的实现了给出了一个resultMap,只不过他的property和column是相同的而已,也就是说,如果数据库中的字段,并不和Coach实体类中的属性值都刚好一一对应的话,是一定要显式的给出resultMap的话,因为你不给出这种关系的话,Mybatis无法通过反射得到column和property的关系,这就意味着我们无法将JavaBean中的属性变成sql语句中?对应的参数的值(ps.setObject(value),最近也在着手写仿Mybatis的框架,可能先在还描述的不是很清楚,其实就是你无法绑定参数了,必须让Mybatis得到column和property的关系)。
我们看resultMap中的具体内容,首先我们不看collection(这个一对多的时候再说)。我们发现除了最基本的result,我们还有一个association,这个属性表示一对一的关系,我们可以看到他比result多一个属性,select(在ibatis中,一对 一和一对多不需要association,collection,select就放在result属性中,其实就我目前看来,似乎mybatis要比ibatis要繁琐一些,再比如result中的nullvalue属性的取消,使用TypeHandler来代替,不过,当然mybatis肯定是在进步的,只是我目前学的太少而已,才没有感受到他的强大)。在这里,我们的select对应的就是可以通过column来查询到Coach的select语句。经过这样的配置,我们便可以成功的来查询数据。

三.实践中的错误
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Error instantiating class com.wangcc.ssm.entity.Team with invalid types () or values (). Cause: java.lang.NoSuchMethodException: com.wangcc.ssm.entity.Team.<init>()

当我为实体类创建了一个有参的构造方法后,我再运行测试类,发现报这个错,很明显(),构造方法出错了,是缺少了无参构造方法。为什么一定要有无参构造方法呢,其实很好理解,首先,我们应该知道ORM框架的核心就是反射和动态代理,后来用了注解之后再可以加一个注解。
这些Java基础技术真的是非常的有用。而这里需要无参构造方法,肯定是和Mybatis通过反射得到实体类对象有关。
因为如果没有无参构造方法,无法通过反射来得到对象,class.newInstance();报错,因为没有无参构造方法,
我们能够通过loadClass得到Class

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值