public User mapRow(ResultSet rs, int rowNum) throws SQLException {
      long id = rs.getLong("userId");

      if (isCacheEnabled() && lookupCache(cacheManager) != null) {
        Element element;
        if ((element = lookupCache(cacheManager).get(DbUtils.hashCodeCacheKeyFor(id))) != null) {
          log.debug("Cache hit on map for User " + id);
          return (User) element.getObjectValue();
        }
      }

      User user = new UserImpl();
      user.setUserId(id);
      user.setActive(rs.getBoolean("active"));
      user.setAdmin(rs.getBoolean("admin"));
      user.setExternal(rs.getBoolean("external"));
      user.setFullName(rs.getString("fullName"));
      user.setInternal(rs.getBoolean("internal"));
      user.setLoginName(rs.getString("loginName"));
      user.setPassword(rs.getString("password"));
      user.setEmail(rs.getString("email"));

      try {
        Blob roleblob = rs.getBlob("roles");
        if (roleblob != null) {
          if (roleblob.length() > 0) {
            byte[] rbytes = roleblob.getBytes(1, (int) roleblob.length());
            String s1 = new String(rbytes);
            String[] roles = s1.split(",");
            user.setRoles(roles);
          }
        }
        if (!isLazy()) {
          user.setGroups(listGroupsByUserId(id));
        }
      } catch (IOException e) {
        e.printStackTrace();
      }

      if (isCacheEnabled() && lookupCache(cacheManager) != null) {
        lookupCache(cacheManager).put(new Element(DbUtils.hashCodeCacheKeyFor(id), user));
      }

      return user;
    }
  @Transactional(readOnly = false, rollbackFor = IOException.class)
  @TriggersRemove(
      cacheName = {"userCache", "lazyUserCache"},
      keyGenerator =
          @KeyGenerator(
              name = "HashCodeCacheKeyGenerator",
              properties = {
                @Property(name = "includeMethod", value = "false"),
                @Property(name = "includeParameterTypes", value = "false")
              }))
  public long saveUser(User user) throws IOException {
    Blob roleBlob = null;
    if (user.getRoles() != null) {
      List<String> roles = new ArrayList<String>(Arrays.asList(user.getRoles()));
      if (user.isExternal() && !roles.contains("ROLE_EXTERNAL")) roles.add("ROLE_EXTERNAL");
      if (user.isInternal() && !roles.contains("ROLE_INTERNAL")) roles.add("ROLE_INTERNAL");
      if (user.isAdmin() && !roles.contains("ROLE_ADMIN")) roles.add("ROLE_ADMIN");
      user.setRoles(roles.toArray(new String[user.getRoles().length]));

      try {
        if (user.getRoles().length > 0) {
          byte[] rbytes = LimsUtils.join(user.getRoles(), ",").getBytes();
          roleBlob = new SerialBlob(rbytes);
        }
      } catch (SerialException e) {
        e.printStackTrace();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }

    MapSqlParameterSource params = new MapSqlParameterSource();
    params
        .addValue("active", user.isActive())
        .addValue("admin", user.isAdmin())
        .addValue("external", user.isExternal())
        .addValue("fullName", user.getFullName())
        .addValue("internal", user.isInternal())
        .addValue("loginName", user.getLoginName())
        .addValue("roles", roleBlob)
        .addValue("email", user.getEmail());

    if (passwordCodecService != null) {
      params.addValue("password", passwordCodecService.encrypt(user.getPassword()));
    } else {
      log.warn(
          "No PasswordCodecService has been wired to this SQLSecurityDAO. This means your passwords may be being "
              + "stored in plaintext, if not already encrypted. Please specify a PasswordCodecService in your Spring config and (auto)wire it "
              + "to this DAO.");
      params.addValue("password", user.getPassword());
    }

    if (user.getUserId() == UserImpl.UNSAVED_ID) {
      SimpleJdbcInsert insert =
          new SimpleJdbcInsert(template).withTableName("User").usingGeneratedKeyColumns("userId");
      Number newId = insert.executeAndReturnKey(params);
      user.setUserId(newId.longValue());
    } else {
      params.addValue("userId", user.getUserId());
      NamedParameterJdbcTemplate namedTemplate = new NamedParameterJdbcTemplate(template);
      namedTemplate.update(USER_UPDATE, params);
    }

    // sort User_Group

    // delete existing joins
    MapSqlParameterSource delparams = new MapSqlParameterSource();
    delparams.addValue("userId", user.getUserId());
    NamedParameterJdbcTemplate namedTemplate = new NamedParameterJdbcTemplate(template);
    namedTemplate.update(USER_GROUP_DELETE_BY_USER_ID, delparams);

    if (user.getGroups() != null && !user.getGroups().isEmpty()) {
      SimpleJdbcInsert eInsert = new SimpleJdbcInsert(template).withTableName("User_Group");
      for (Group g : user.getGroups()) {
        MapSqlParameterSource ugParams = new MapSqlParameterSource();
        ugParams
            .addValue("users_userId", user.getUserId())
            .addValue("groups_groupId", g.getGroupId());

        eInsert.execute(ugParams);
      }
    }

    return user.getUserId();
  }